--- /dev/null
+cmus (2.12.0-2) unstable; urgency=medium
+
+ * Team upload
+ * Fix build with ffmpeg 8.0 (Closes: #1115013)
+
+ -- Sebastian Ramacher <sramacher@debian.org> Fri, 12 Sep 2025 23:00:35 +0200
+
+cmus (2.12.0-1) unstable; urgency=medium
+
+ * Team upload.
+
+ [ Philippe SWARTVAGHER ]
+ * New upstream version 2.12.0 (Closes: #1087737)
+ + Refresh patches
+ * Bump standards-version to 4.7.2: no change needed
+ * d/rules: remove as-needed linker flag
+ * Add lintian override for library-not-linked-against-libc
+ of usr/lib/cmus/op/sndio.so
+ * Add patch to use hardening flags to build Doc/ttman
+
+ [ Helmut Grohne ]
+ * Fix FTCBFS: Fix build/host confusion. (Closes: #954749)
+
+ [ Dylan Aïssi ]
+ * Convert debian/copyright to DEP5
+
+ -- Philippe SWARTVAGHER <phil.swart@gmx.fr> Sat, 15 Mar 2025 22:56:48 +0100
+
+cmus (2.11.0-1) unstable; urgency=medium
+
+ * Team upload
+ * New upstream version 2.11.0
+ - Fix build with ffmpeg 7.0 (Closes: #1072405)
+ * debian/patches: Refresh patches
+ * debian/control:
+ - Bump Standards-Version
+ - Use pkgconf
+ - Use openmpt's modplug
+ - Use libncurses-dev
+
+ -- Sebastian Ramacher <sramacher@debian.org> Sun, 09 Jun 2024 23:45:17 +0200
+
+cmus (2.10.0-4) unstable; urgency=medium
+
+ * Team upload
+ * debian/patches: Apply upstream patch to fix compatibility with ffmpeg 6.0
+ (Closes: #1041375)
+
+ -- Sebastian Ramacher <sramacher@debian.org> Thu, 27 Jul 2023 21:00:31 +0200
+
+cmus (2.10.0-3) unstable; urgency=medium
+
+ * Team upload
+ * debian/control: Bump Standards-Version
+ * debian/: Remove roar support (Closes: #1030661)
+
+ -- Sebastian Ramacher <sramacher@debian.org> Tue, 07 Feb 2023 09:20:50 +0100
+
+cmus (2.10.0-2) unstable; urgency=medium
+
+ * Team upload
+ * Add a patch from upstream to fix a freeze when exiting cmus
+
+ -- Philippe SWARTVAGHER <phil.swart@gmx.fr> Sun, 31 Jul 2022 22:01:04 +0200
+
+cmus (2.10.0-1) unstable; urgency=medium
+
+ * Team upload
+
+ [ Jenkins ]
+ * Remove constraints unnecessary since buster
+
+ [ Philippe SWARTVAGHER ]
+ * New upstream version 2.10.0
+ * Bump d/watch version
+ * Bump standards-version to 4.6.1
+ * Add a patch to fix a typo spotted by Lintian
+
+ -- Philippe SWARTVAGHER <phil.swart@gmx.fr> Wed, 13 Jul 2022 20:40:37 +0200
+
+cmus (2.9.1-1) unstable; urgency=medium
+
+ * Team upload
+ * New upstream release
+
+ -- Sebastian Ramacher <sramacher@debian.org> Fri, 22 Jan 2021 22:28:04 +0100
+
+cmus (2.9.0-1) unstable; urgency=medium
+
+ * Team upload
+
+ [ Debian Janitor ]
+ * Set upstream metadata fields: Bug-Database, Bug-Submit, Repository,
+ Repository-Browse.
+
+ [ Patrick Gaskin ]
+ * Fix missing dependency for MPRIS support
+
+ [ Sebastian Ramacher ]
+ * New upstream release
+ * debian/control:
+ - Bump Standards-Version
+ - Bump debhelper compat to 13
+ - Set RRR: no
+ * debian/patches/12-typos.patch: Removed, fixed upstream
+
+ -- Sebastian Ramacher <sramacher@debian.org> Tue, 19 Jan 2021 20:15:26 +0100
+
+cmus (2.8.0-2) unstable; urgency=medium
+
+ * Set build flags via /usr/share/dpkg/buildflags.mk
+ * Link with -latomic to fix FTBFS on various architectures (Closes: #935678)
+
+ -- Ryan Kavanagh <rak@debian.org> Sat, 07 Sep 2019 10:37:13 -0400
+
+cmus (2.8.0-1) unstable; urgency=medium
+
+ [ Ondřej Nový ]
+ * Use debhelper-compat instead of debian/compat
+
+ [ Helmut Grohne ]
+ * Fix FTCBFS: Supply a cross environment to ./configure. (Closes: #911163)
+
+ [ Ryan Kavanagh ]
+ * New upstream version 2.8.0 (Closes: #932107, #783498)
+ + Update copyright file with new holders
+ + Drop 12-ffmpeg-4.0.patch (applied upstream)
+ + Refresh patches
+ * Drop outdated get-orig-source target from rules
+ * Fix typos in source, 12-typos.patch
+ * Enable hardening
+ * Bump debphelper compat to 12
+ * Bump standards-version to 4.4.0
+ * cmus-plugin-ffmpeg (<< 2.8.0) breaks cmus (>= 2.8.0)
+
+ -- Ryan Kavanagh <rak@debian.org> Sat, 17 Aug 2019 14:51:27 -0400
+
+cmus (2.7.1+git20160225-2) unstable; urgency=medium
+
+ * Team upload.
+
+ [ James Cowgill ]
+ * Add upstream patch to fix FTBFS with FFmpeg 4.0. (Closes: #888384)
+ * d/changelog: Remove trailing blank line.
+
+ [ Alessio Treglia ]
+ * Remove myself from the Uploaders field.
+
+ [ Ondřej Nový ]
+ * d/control: Set Vcs-* to salsa.debian.org.
+ * d/changelog: Remove trailing whitespaces.
+
+ [ Felipe Sateler ]
+ * Change maintainer address to debian-multimedia@lists.debian.org.
+
+ -- James Cowgill <jcowgill@debian.org> Thu, 17 May 2018 11:34:35 +0100
+
+cmus (2.7.1+git20160225-1) unstable; urgency=medium
+
+ * Team upload.
+ * New upstream snapshot.
+ - Fix build against ffmpeg 3.0. (Closes: #810557)
+ * debian/patches/{01_config.mk.diff,02_link_avcodec.patch}: Removed, applied
+ upstream.
+ * debian/control:
+ - Bump Standards Version.
+ - Update Vcs-Git.
+
+ -- Sebastian Ramacher <sramacher@debian.org> Sun, 06 Mar 2016 21:37:57 +0100
+
+cmus (2.7.1-1) unstable; urgency=medium
+
+ * Team upload.
+
+ [ Alessio Treglia ]
+ * Demote extra plugins to Suggests (Closes: #789256)
+ * Refresh patchset for 2.6.0.
+
+ [ Sebastian Ramacher ]
+ * New upstream release. (Closes: #779335, #792134)
+ - Use libswresample instead of libavresample. (Closes: #805169, #805109)
+ * Update path for README
+ * debian/control:
+ - Add libdiscid-dev, libopusfile-dev, libsamplerate0-dev and libjack-dev
+ to Build-Depends.
+ - Change libavresample-dev to libswresample-dev in Build-Depends and add
+ libavcodec-dev.
+ - Add bash-completion to Build-Depends.
+ - Bump Standards-Version to 3.9.6.
+ - Update Vcs-Browser.
+ - Make cmus-plugin-ffmpeg depend on the same version of cmus (Closes:
+ #695072)
+ * debian/cmus.install: Install zsh completion.
+ * debian/cmus.bash-completion: Install bash completion.
+ * debian/rules:
+ - Build with --parallel and --with bash-completion.
+ - Handle jack shlibs similar to pulse.
+ * debian/patches:
+ - libav10.patch: Removed, no longer needed.
+ - 02_link_avcodec.patch: Functions from libavcodec are used so make sure
+ the ffmpeg plugin is linked against libavcodec.
+
+ -- Sebastian Ramacher <sramacher@debian.org> Sun, 15 Nov 2015 19:24:52 +0100
+
+cmus (2.5.0-7) unstable; urgency=medium
+
+ * Re-introduce Roaraudio support (Closes: #680745):
+ - debian/control: Add build-dependency on libroad-dev.
+ - debian/rules: Tune dpkg-shlibdeps call to move pulse and roar's
+ dependencies to Recommends. Made the whole mechanism slightly more
+ elegant.
+ * Enable CUE support.
+ * The project has moved to github, update the Homepage field accordingly.
+ * Update debian/watch, project has moved from sourceforge to github.
+
+ -- Alessio Treglia <alessio@debian.org> Thu, 14 Aug 2014 13:45:10 +0100
+
+cmus (2.5.0-6) unstable; urgency=medium
+
+ * Team upload.
+ * Upload to unstable.
+
+ -- Sebastian Ramacher <sramacher@debian.org> Sun, 11 May 2014 23:45:16 +0200
+
+cmus (2.5.0-5) experimental; urgency=low
+
+ * Team upload.
+ * Compile against libav10 (Closes: #739301)
+ * Bump standards version
+
+ -- Reinhard Tartler <siretart@tauware.de> Mon, 24 Mar 2014 19:35:28 -0400
+
+cmus (2.5.0-4) unstable; urgency=low
+
+ [ Ryan Kavanagh ]
+ * Patches were applied upstream
+
+ [ Alessio Treglia ]
+ * Add patch to prevent FTBFS. (Closes: #724181)
+
+ -- Alessio Treglia <alessio@debian.org> Sun, 06 Oct 2013 20:46:25 +0100
+
+cmus (2.5.0-3) unstable; urgency=low
+
+ * Don't FTBFS due to missing config.mk (Closes: #720781), 01_config.mk.diff
+ * Fix typo in cmus binary, 02_fix_typo.diff
+ * Enable build hardening
+ + Bump debhelper version to 9.0.0 and compat to 9
+ + Introduce 03_cppflags.diff to use CPPFLAGS; needed for function
+ fortification
+ + Override hardening-no-fortify-functions false positives
+ * Bump standards version to 3.9.4
+ * Switch to canonical Vcs-* fields
+ * Enable verbose build logs
+
+ -- Ryan Kavanagh <rak@debian.org> Thu, 29 Aug 2013 13:48:30 -0400
+
+cmus (2.5.0-2) unstable; urgency=low
+
+ * Upload to unstable.
+
+ -- Alessio Treglia <alessio@debian.org> Sat, 11 May 2013 01:21:18 +0200
+
+cmus (2.5.0-1) experimental; urgency=low
+
+ * New upstream release:
+ - CUE sheets support.
+ - cdio input plugin.
+ - support for WavPack `.wvc` correction files.
+ - new «zenburn» color scheme and text attributes (bold/reverse/...)
+ support for UI elements.
+ - improved tab completion, new scroll_offset and icecast_default_charset
+ options, even better tag parsing and compilations handling, and
+ numerous small enhancements all over the place.
+ * Build-depend on libcddb2-dev,libcdio-cdda-dev.
+
+ -- Alessio Treglia <alessio@debian.org> Thu, 15 Nov 2012 00:28:20 +0000
+
+cmus (2.4.3-2) unstable; urgency=low
+
+ [ Ryan Kavanagh ]
+ * Update my email address to @debian.org
+ * Drop DM-Upload-Allowed: yes, no longer needed
+
+ [ Alessio Treglia ]
+ * Build cmus without roar support. (Closes: #675610)
+ * Bump Standards.
+
+ -- Alessio Treglia <alessio@debian.org> Sat, 02 Jun 2012 20:07:57 +0200
+
+cmus (2.4.3-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Alessio Treglia <alessio@debian.org> Sat, 03 Dec 2011 12:55:46 +0100
+
+cmus (2.4.2-1) unstable; urgency=low
+
+ * New upstream release.
+ * Drop 0001-fix-compile-error-for-new-versions-of-ffmpeg.patch,
+ applied upstream.
+
+ -- Alessio Treglia <alessio@debian.org> Tue, 26 Jul 2011 10:13:25 +0200
+
+cmus (2.4.1-2) unstable; urgency=low
+
+ * Add Ubuntu-specific patch to fix FTBFS with newest version of ffmpeg.
+ * Replace negated list of architectures with linux-any (Closes: #634706).
+
+ -- Alessio Treglia <alessio@debian.org> Sat, 23 Jul 2011 10:48:29 +0200
+
+cmus (2.4.1-1) unstable; urgency=medium
+
+ * New upstream release (Closes: #628422):
+ - Doc: add help for :shell
+ - ffmpeg: move up "config/ffmpeg.h" include
+ - fix two memleaks
+ - fix cache refresh bug
+ - configure: fix FLAC include path
+ - configure: fix ffmpeg header detection
+ - fix TCP/IP networking protocol
+ - fix segfault when hitting win-activate on empty tree
+ - display error if seeking failed
+ - fix segfault when using tqueue/lqueue
+ - fix lqueue command
+ - fix infinite loop when adding certain mp3 files
+ - fix reading of id3v2 tags at the end of files
+ - more fault-tolerant integer tag-reading
+ * Bump urgency to medium as the previous release was seriously buggy.
+
+ -- Alessio Treglia <alessio@debian.org> Sun, 29 May 2011 19:18:48 +0200
+
+cmus (2.4.0-1) unstable; urgency=low
+
+ * New upstream release "Easter egg":
+ - Mutt-like short filters.
+ - Live filtering.
+ - Resume support.
+ - Smarter string handling.
+ - Long format options, including ones for bitrate/codec.
+ - HTTP proxy support for streams via http_proxy environment variable.
+ - Less CPU wakeups during playback.
+ - New RoarAudio output plugin.
+ - Support for big-endian systems, lots of different audio sample formats,
+ almost any C compiler and unix-like OS out there.
+ - Various bugfixes.
+ - Full release notes are available at:
+ http://sourceforge.net/mailarchive/message.php?msg_id=27403242
+ * debian/watch: Properly handle release-candidate,beta releases.
+ * Remove debian/patches directory, all patches have been applied upstream.
+ * Bump Standards.
+
+ -- Alessio Treglia <alessio@debian.org> Tue, 26 Apr 2011 23:30:07 +0200
+
+cmus (2.3.5-1) unstable; urgency=low
+
+ * New upstream release:
+ - Features gapless MP3 playback.
+ - Native PulseAudio support.
+ - Faster startup.
+ - Improve buildsystem.
+ * Refresh patches.
+ * Remo 21-missing_plugins.patch, applied upstream.
+
+ -- Alessio Treglia <alessio@debian.org> Sat, 23 Apr 2011 09:56:57 +0200
+
+cmus (2.3.4-3) unstable; urgency=low
+
+ * Handle missing dependencies more gracefully:
+ - cmus silently skips plugins with missing dependencies, and instead
+ outputs a debug message. Original patch by Johannes Weißl, already
+ accepted upstream.
+
+ -- Alessio Treglia <alessio@debian.org> Fri, 01 Apr 2011 08:59:01 +0200
+
+cmus (2.3.4-2) unstable; urgency=low
+
+ * Avoid to depend on several sound servers (Closes: #612887) and let
+ users choose to rely on the favorite one.
+ - debian/control:
+ + Add shlibs:Recommends field.
+ - debian/rules:
+ + Supply {dh_,dpkg-}shlibdeps with proper options to demote roar
+ and pulse audio dependencies to Recommends.
+
+ -- Alessio Treglia <alessio@debian.org> Tue, 15 Mar 2011 12:46:03 +0100
+
+cmus (2.3.4-1) unstable; urgency=low
+
+ [ Ryan Kavanagh ]
+ * New upstream release.
+ * Dropped 01_spelling_mistakes.diff, 02_cmus-tutorial_whatis.diff
+ and 03-terminal_corruption.patch (applied upstream).
+ * Refreshed 10-roaraudio_support.patch
+ * Bump my copyright
+
+ [ Alessio Treglia ]
+ * Add DM-Upload-Allowed: yes.
+
+ -- Ryan Kavanagh <ryanakca@kubuntu.org> Tue, 22 Feb 2011 09:03:23 -0500
+
+cmus (2.3.3-4) unstable; urgency=low
+
+ * Upload to unstable.
+
+ -- Alessio Treglia <alessio@debian.org> Wed, 09 Feb 2011 12:05:49 +0100
+
+cmus (2.3.3-3) experimental; urgency=low
+
+ * Add RoarOutput plugin (Closes: #609202), thanks to
+ Philipp Schafft <lion@lion.leolix.org> for the patch.
+ * Add patch taken from upstream's git to fix segfault when adding to
+ queue.
+ * Build-depends on libroar-dev (>= 0.4~beta2).
+
+ -- Alessio Treglia <alessio@debian.org> Mon, 17 Jan 2011 02:23:04 +0100
+
+cmus (2.3.3-2) unstable; urgency=low
+
+ * Prevent terminal corruption on track change.
+ * debian/copyright: Update sources download location.
+ * Split cmus to provide smart dependencies (Closes: #442423).
+ * Bump Standards.
+
+ -- Alessio Treglia <alessio@debian.org> Sun, 01 Aug 2010 12:26:08 +0200
+
+cmus (2.3.3-1) unstable; urgency=low
+
+ [ Ryan Kavanagh ]
+ * New upstream release (Closes: #572284)
+ * Imported Upstream version 2.3.3
+ * Changed to source format 3.0 source. Involved converting
+ dpatch->quilt and dropping dpatch B-D.
+ * Dropped 01_cmusffmpeg.diff, no longer needed. Upstream checks for
+ ffmpeg and falls back to libavcodec for us.
+ * Dropped 02_cmusstatusdisplay.diff, included upstream.
+ * Dropped Yavor's mpcdec patch. Upstream expanded on it, using his
+ code if building under MPC SV8, the old code otherwise.
+ * Updated debian/watch
+ * Updated copyright file with new copyright holders and download
+ location.
+ * This is a new upload, package will thus be rebuilt against libavformat.
+ (Closes: #568361).
+ * Dropped debian/patches directory
+ * Move to debhelper 7 rules
+ * Now standards-version 3.8.4
+ * Fix debhelper-but-no-misc-depends lintian warning
+ * Override dh_auto_configure because upstream uses a homebrewed configure
+ script that doesn't accept --a=b style options
+ * Bump debhelper version to (>= 7.0.50~) because we're using override_dh_*
+ * Fix spelling mistakes in binary and documentation
+ (01_spelling_mistake.diff)
+ * Fix whatis entry for cmus-tutorial manpage (02_cmus-tutorial_whatis.diff)
+ * Added Julien Louis' and my own packaging copyright blurb to
+ debian/copyright.
+
+ [ Alessio Treglia ]
+ * This isn't a non-maintainer upload, we adopt this (Closes: #587604).
+ * Add debian/gbp.conf file.
+ * debian/control:
+ - Add Vcs-* tags.
+ - Bump Standards.
+ - Lines should be shorter than 80 characters.
+ - Add myself to Uploaders field, I'll take care of sponsoring this in
+ future.
+ - Build-depend on pkg-config.
+ - Drop unnecessary Recommends field.
+ * Drop aRTs support as it is no longer maintained.
+ * Add PulseAudio support.
+ * debian/rules:
+ - Call configure script instead of relying on dh_auto_configure.
+ * Drop README.source, we don't rely on dpatch as patch system.
+ * Update debian/copyright.
+
+ -- Alessio Treglia <alessio@debian.org> Sun, 04 Jul 2010 20:06:58 +0200
+
+cmus (2.2.0-4.1) unstable; urgency=low
+
+ * NMU
+ * Patch from Yavor Doganov to port cmus to the new mpcdec API,
+ thereby allowing cmus to build from source again.
+ closes: #476382, #552820.
+
+ -- Clint Adams <schizo@debian.org> Sun, 31 Jan 2010 00:03:40 -0500
+
+cmus (2.2.0-4) unstable; urgency=low
+
+ * Updated debian/watch file Closes: #449897
+ - Thanks to Raphael Geissert <atomo64@gmail.com>
+ * Fix the ffmpeg/avcodec.h includes Closes: #517570
+ - Thanks to Cyril Brulebois <kibi@debian.org>
+ * Added Recommends libasound2, libartsc0, libao2 Closes: #439719
+ * Added Depends line in debian/control
+ * Added dpatch on Build-Depends on debian/control
+ * Deleted commented lines on debian/rules
+ * Update debian/rules for dpatch dependence
+ * Changed debian/control
+ - Standard-Version to 3.8.1 ( was 3.8.0 )
+ - Added debian/README.source
+ - Updated to debhelper to 7
+ - Updated debian/compat (was 5)
+ - Added Homepage field
+
+ -- Carlos Eduardo Sotelo Pinto (krlos) <krlos.aqp@gmail.com> Thu, 19 Mar 2009 13:38:16 -0500
+
+cmus (2.2.0-3) unstable; urgency=low
+
+ * Acknowledging NMU. Closes: #509277.
+
+ -- Carlos Eduardo Sotelo Pinto (krlos) <krlos.aqp@gmail.com> Mon, 29 Dec 2008 22:01:01 +0100
+
+cmus (2.2.0-2) unstable; urgency=low
+
+ * New maintainer. Closes: #484734
+ * Changed debian/control
+ - Standard-Version to 3.8.0 ( was 3.7.2 no changes needed )
+
+ -- Carlos Eduardo Sotelo Pinto (krlos) <krlos.aqp@gmail.com> Wed, 03 Sep 2008 17:46:50 -0500
+
+cmus (2.2.0-1.1) unstable; urgency=high
+
+ * Non-maintainer upload by the Security Team.
+ * Modify example script cmus-status-display to write the current
+ status to .cmus-status in the user's home instead of /tmp/cmus-status,
+ since the latter could lead to symlink attacks. CVE-2008-5375
+ (Closes: #509277)
+
+ -- Moritz Muehlenhoff <jmm@debian.org> Sun, 28 Dec 2008 14:57:06 +0100
+
+cmus (2.2.0-1) unstable; urgency=low
+
+ * New upstream release
+ * Add libwavpack-dev and libavformat.dev to Build-Depends
+ * Update debian/copyright
+
+ -- Julien Louis <ptitlouis@sysif.net> Fri, 27 Jul 2007 21:54:36 +0200
+
+cmus (2.1.0-2) unstable; urgency=low
+
+ * Rebuild against libflac8 (Closes: #426638).
+
+ -- Julien Louis <ptitlouis@sysif.net> Mon, 04 Jun 2007 22:56:58 +0200
+
+cmus (2.1.0-1) unstable; urgency=low
+
+ * New upstream release (closes: #399965).
+ * Updated debian/copyright
+ * Added libfaad-dev to Build-Depends
+
+ -- Julien Louis <ptitlouis@sysif.net> Thu, 21 Dec 2006 20:25:59 +0100
+
+cmus (2.0.4-1) unstable; urgency=low
+
+ * New upstream release.
+ * Added debian/watch file.
+ * Build-Depends agains libasound2-dev (>= 1.0.11).
+
+ -- Julien Louis <ptitlouis@sysif.net> Wed, 23 Aug 2006 03:34:04 +0200
+
+cmus (2.0.3-3) unstable; urgency=low
+
+ * Drop libasound2-dev Build Dependency on non-linux arches
+ (Closes: #377885).
+
+ -- Julien Louis <ptitlouis@sysif.net> Wed, 12 Jul 2006 01:06:02 +0200
+
+cmus (2.0.3-2) unstable; urgency=low
+
+ * Move all dh_* helper stuff in the binary-arch target (Closes: #376320).
+ * Remove debian/patches/01_asciidoc_xsl_path.dpatch since it is not usefull.
+ * Remove dpatch from Build-Depends.
+
+ -- Julien Louis <ptitlouis@sysif.net> Sun, 2 Jul 2006 14:16:03 +0200
+
+cmus (2.0.3-1) unstable; urgency=low
+
+ * New upstream release.
+ * Remove asciidoc, docbook-xsl, doxbook-xml ans xsltproc from Build-Depends.
+ * Add libartsc0-dev and libao-dev to Build-Depends.
+
+ -- Julien Louis <ptitlouis@sysif.net> Sun, 18 Jun 2006 17:26:44 +0200
+
+cmus (2.0.2-1) unstable; urgency=low
+
+ * New upstream release.
+ * Remove ASCIIDOC patch since upstream now search for
+ /etc/asciidoc/docbook-xsl/.
+ * Add docbook-xml to Build-Depends.
+ * Bump Standards-Version no change needed.
+ * Added REAMDE.Debian
+
+ -- Julien Louis <ptitlouis@sysif.net> Tue, 30 May 2006 22:12:01 +0200
+
+cmus (2.0.0-1) unstable; urgency=low
+
+ * Initial release (Closes: #340000)
+
+ -- Julien Louis <ptitlouis@sysif.net> Mon, 7 Nov 2005 18:19:55 +0100
--- /dev/null
+contrib/cmus.bash-completion cmus
--- /dev/null
+usr
+contrib/_cmus usr/share/zsh/vendor-completions
--- /dev/null
+cmus: hardening-no-fortify-functions usr/lib/cmus/ip/flac.so
+
+## This library actually doesn't directly call any libc function
+cmus: library-not-linked-against-libc [usr/lib/cmus/op/sndio.so]
--- /dev/null
+Source: cmus
+Section: sound
+Priority: optional
+Maintainer: Debian Multimedia Maintainers <debian-multimedia@lists.debian.org>
+Uploaders:
+ Ryan Kavanagh <rak@debian.org>
+Build-Depends:
+ debhelper-compat (= 13),
+ bash-completion,
+ libao-dev,
+ libasound2-dev [linux-any],
+ libavcodec-dev,
+ libavformat-dev,
+ libswresample-dev,
+ libcddb2-dev,
+ libcdio-cdda-dev,
+ libcue-dev,
+ libdiscid-dev,
+ libfaad-dev,
+ libflac-dev,
+ libjack-dev,
+ libmad0-dev,
+ libmpcdec-dev,
+ libncurses-dev,
+ libopenmpt-modplug-dev,
+ libopusfile-dev,
+ libpulse-dev,
+ libsamplerate0-dev,
+ libsystemd-dev,
+ libvorbis-dev,
+ libwavpack-dev,
+ pkgconf
+Standards-Version: 4.7.2
+Homepage: https://cmus.github.io/
+Vcs-Git: https://salsa.debian.org/multimedia-team/cmus.git
+Vcs-Browser: https://salsa.debian.org/multimedia-team/cmus
+Rules-Requires-Root: no
+
+Package: cmus
+Architecture: any
+Depends:
+ ${misc:Depends},
+ ${shlibs:Depends}
+Recommends:
+ cmus-plugin-ffmpeg
+Suggests:
+ ${shlibs:Suggests}
+Breaks: cmus-plugin-ffmpeg (<< 2.8.0)
+Description: lightweight ncurses audio player
+ C* Music Player is a modular and very configurable ncurses-based audio player.
+ It has some interesting features like configurable colorscheme, mp3 and ogg
+ streaming, it can be controlled with an UNIX socket, filters, album/artists
+ sorting and a vi-like configuration interface.
+ .
+ It currently supports different input formats:
+ - Ogg Vorbis
+ - MP3 (with libmad)
+ - FLAC
+ - Wav
+ - Modules (with libmodplug)
+ - Musepack
+ - AAC
+ - Windows Media Audio
+
+Package: cmus-plugin-ffmpeg
+Architecture: any
+Depends:
+ ${misc:Depends},
+ ${shlibs:Depends},
+ cmus (= ${binary:Version})
+Description: lightweight ncurses audio player (FFmpeg plugin)
+ C* Music Player is a modular and very configurable ncurses-based audio player.
+ It has some interesting features like configurable colorscheme, mp3 and ogg
+ streaming, it can be controlled with an UNIX socket, filters, album/artists
+ sorting and a vi-like configuration interface.
+ .
+ This package adds FFmpeg support to C* Music Player.
--- /dev/null
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Source: https://github.com/cmus/cmus
+
+Files: *
+Copyright: 2004-2007 Timo Hirvonen <tihirvon@gmail.com>
+ 2009 Gregory Petrosyan <gregory.petrosyan@gmail.com>
+ 1999 Paul N. Fisher <rao@gnu.org>
+ 2002 Andy Lo A Foe <andy@alsaplayer.org>
+ 2006 dnk <dnk@bjum.net>
+ 2006 Chun-Yu Shei <cshei AT cs.indiana.edu>
+ 2007 Kevin Ko <kevin.s.ko@gmail.com>
+ 2007 dnk <dnk@bjum.net>
+ 2007 Johannes Weißl
+ 2016 Nic Soudée
+License: GPL-2+
+
+Files: debian/*
+Copyright: 2005-2008 Julien Louis <ptitlouis@sysif.net>
+ 2010-2019 Ryan Kavanagh <ryanakca@kubuntu.org>
+ 2010 Alessio Treglia <alessio@debian.org>
+License: GPL-2+
+
+License: GPL-2+
+ This package is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+ .
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the full text of the GNU General Public License
+ version 2 can be found in `/usr/share/common-licenses/GPL-2'.
--- /dev/null
+README.md
+contrib/
--- /dev/null
+cmus-status-display
--- /dev/null
+[DEFAULT]
+pristine-tar = True
--- /dev/null
+From: Ryan Kavanagh <rak@debian.org>
+Date: Thu, 27 Jul 2023 20:59:47 +0200
+Subject: Pass LDLIBS to the linker
+
+Origin: Debian
+Bug-Debian: http://bugs.debian.org/935678
+Forwarded: no
+Reviewed-by: Ryan Kavanagh <rak@debian.org>
+Last-Update: 2019-09-07
+
+Needed to pass -latomic at the end so that we can fix a FTBFS on various
+architectures.
+Last-Update: 2019-09-07
+---
+ Makefile | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/Makefile b/Makefile
+index 76a4d59..720f043 100644
+--- a/Makefile
++++ b/Makefile
+@@ -19,7 +19,7 @@ include scripts/lib.mk
+ CFLAGS += -D_FILE_OFFSET_BITS=64
+
+ CMUS_LIBS = $(PTHREAD_LIBS) $(NCURSES_LIBS) $(ICONV_LIBS) $(DL_LIBS) $(DISCID_LIBS) \
+- -lm $(COMPAT_LIBS) $(LIBSYSTEMD_LIBS)
++ -lm $(COMPAT_LIBS) $(LIBSYSTEMD_LIBS) $(LDLIBS)
+
+ command_mode.o input.o main.o ui_curses.o op/pulse.lo: .version
+ command_mode.o input.o main.o ui_curses.o op/pulse.lo: CFLAGS += -DVERSION=\"$(VERSION)\"
--- /dev/null
+Description: Use hardening flags for Doc/ttman
+Author: Philippe SWARTVAGHER <phil.swart@gmx.fr>
+Forwarded: not-needed
+Last-Update: 2025-03-16
+---
+diff --git a/Makefile b/Makefile
+index 76a4d59..3860f77 100644
+--- a/Makefile
++++ b/Makefile
+@@ -255,12 +255,6 @@ $(man7): Doc/ttman
+ %.7: %.txt
+ $(call cmd,ttman)
+
+-Doc/ttman.o: Doc/ttman.c
+- $(call cmd,hostcc,)
+-
+-Doc/ttman: Doc/ttman.o
+- $(call cmd,hostld,)
+-
+ quiet_cmd_ttman = MAN $@
+ cmd_ttman = Doc/ttman $< $@
+ # }}}
--- /dev/null
+From: ihy123 <aladinandreyy@gmail.com>
+Date: Thu, 14 Aug 2025 12:44:10 +0300
+Subject: ip/ffmpeg: more precise seeking
+
+av_seek_frame() and avformat_seek_file() seek to nearest "keyframe". For
+codecs like, for example, ape this means that seeking will be very off
+(5 seconds or more). So what we do is:
+1. seek to nearest "keyframe" before the desired time,
+2. discard some frames to approach the desired time.
+---
+ ip/ffmpeg.c | 154 +++++++++++++++++++++++++++++++++++++-----------------------
+ 1 file changed, 94 insertions(+), 60 deletions(-)
+
+diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
+index 21b9a01..ecbf005 100644
+--- a/ip/ffmpeg.c
++++ b/ip/ffmpeg.c
+@@ -44,6 +44,8 @@ struct ffmpeg_input {
+ AVPacket pkt;
+ int curr_pkt_size;
+ uint8_t *curr_pkt_buf;
++ int64_t seek_ts;
++ int64_t prev_frame_end;
+ int stream_index;
+
+ unsigned long curr_size;
+@@ -76,6 +78,8 @@ static struct ffmpeg_input *ffmpeg_input_create(void)
+ return NULL;
+ }
+ input->curr_pkt_size = 0;
++ input->seek_ts = -1;
++ input->prev_frame_end = -1;
+ input->curr_pkt_buf = input->pkt.data;
+ return input;
+ }
+@@ -314,10 +318,7 @@ static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data, AVFormatContext
+ #else
+ AVFrame *frame = avcodec_alloc_frame();
+ #endif
+- int got_frame;
+ while (1) {
+- int len;
+-
+ if (input->curr_pkt_size <= 0) {
+ #if LIBAVCODEC_VERSION_MAJOR >= 56
+ av_packet_unref(&input->pkt);
+@@ -333,78 +334,108 @@ static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data, AVFormatContext
+ #endif
+ return 0;
+ }
+- if (input->pkt.stream_index == input->stream_index) {
+- input->curr_pkt_size = input->pkt.size;
+- input->curr_pkt_buf = input->pkt.data;
+- input->curr_size += input->pkt.size;
+- input->curr_duration += input->pkt.duration;
+- }
+- continue;
+- }
+
+- {
+- AVPacket avpkt;
+- av_new_packet(&avpkt, input->curr_pkt_size);
+- memcpy(avpkt.data, input->curr_pkt_buf, input->curr_pkt_size);
++ if (input->pkt.stream_index != input->stream_index)
++ continue;
++ input->curr_pkt_size = input->pkt.size;
++ input->curr_pkt_buf = input->pkt.data;
++ input->curr_size += input->pkt.size;
++ input->curr_duration += input->pkt.duration;
++
+ #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
+- int send_result = avcodec_send_packet(cc, &avpkt);
+- if (send_result != 0) {
+- if (send_result != AVERROR(EAGAIN)) {
+- d_print("avcodec_send_packet() returned %d\n", send_result);
+- char errstr[AV_ERROR_MAX_STRING_SIZE];
+- if (!av_strerror(send_result, errstr, AV_ERROR_MAX_STRING_SIZE ))
+- {
+- d_print("av_strerror(): %s\n", errstr);
+- } else {
+- d_print("av_strerror(): Description for error cannot be found\n");
+- }
+- av_packet_unref(&avpkt);
+- return -IP_ERROR_INTERNAL;
++ int send_result = avcodec_send_packet(cc, &input->pkt);
++ if (send_result != 0 && send_result != AVERROR(EAGAIN)) {
++ d_print("avcodec_send_packet() returned %d\n", send_result);
++ char errstr[AV_ERROR_MAX_STRING_SIZE];
++ if (!av_strerror(send_result, errstr, AV_ERROR_MAX_STRING_SIZE ))
++ {
++ d_print("av_strerror(): %s\n", errstr);
++ } else {
++ d_print("av_strerror(): Description for error cannot be found\n");
+ }
+- len = 0;
+- } else {
+- len = input->curr_pkt_size;
++ return -IP_ERROR_INTERNAL;
+ }
+-
+- int recv_result = avcodec_receive_frame(cc, frame);
+- got_frame = (recv_result == 0) ? 1 : 0;
+-#else
+- len = avcodec_decode_audio4(cc, frame, &got_frame, &avpkt);
+-#endif
+-#if LIBAVCODEC_VERSION_MAJOR >= 56
+- av_packet_unref(&avpkt);
+-#else
+- av_free_packet(&avpkt);
+ #endif
+ }
++
++#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
++ int recv_result = avcodec_receive_frame(cc, frame);
++ if (recv_result < 0) {
++ input->curr_pkt_size = 0;
++ continue;
++ }
++#else
++ int got_frame;
++ int len = avcodec_decode_audio4(cc, frame, &got_frame, &input->pkt);
+ if (len < 0) {
+ /* this is often reached when seeking, not sure why */
+ input->curr_pkt_size = 0;
+ continue;
+ }
+- input->curr_pkt_size -= len;
+- input->curr_pkt_buf += len;
+- if (got_frame) {
+- int res = swr_convert(swr,
+- &output->buffer,
+- frame->nb_samples,
+- (const uint8_t **)frame->extended_data,
+- frame->nb_samples);
+- if (res < 0)
+- res = 0;
+- output->buffer_pos = output->buffer;
++ if (!got_frame)
++ continue;
++#endif
++
++ int64_t frame_ts = -1;
++ if (frame->pts)
++ frame_ts = frame->pts;
++ else if (frame->pkt_pts)
++ frame_ts = frame->pkt_pts;
++ else if (frame->pkt_dts)
++ frame_ts = frame->pkt_dts;
++
++ const uint8_t **in = (const uint8_t **)frame->extended_data;
++ int in_count = frame->nb_samples;
++ if (input->seek_ts > 0 && (frame_ts >= 0 || input->prev_frame_end >= 0)) {
++ struct ffmpeg_private *priv = ip_data->private;
++ AVStream *st = priv->input_context->streams[priv->input->stream_index];
++ if (frame_ts >= 0)
++ frame_ts = av_rescale_q(frame_ts, st->time_base, AV_TIME_BASE_Q);
++ else
++ frame_ts = input->prev_frame_end;
++ int64_t frame_dur = av_rescale(frame->nb_samples, AV_TIME_BASE, sf_get_rate(ip_data->sf));
++ int64_t frame_end = frame_ts + frame_dur;
++ input->prev_frame_end = frame_end;
++ d_print("seek_ts: %ld, frame_ts: %ld, frame_end: %ld\n", input->seek_ts, frame_ts, frame_end);
++ if (frame_end <= input->seek_ts)
++ continue;
++
++ /* skip part of this frame */
++ int64_t skip_samples = av_rescale(input->seek_ts - frame_ts, sf_get_rate(ip_data->sf), AV_TIME_BASE);
++ in_count -= skip_samples;
++ if (av_sample_fmt_is_planar(frame->format)) {
++ for (int i = 0; i < cc->channels; i++) {
++ in[i] += skip_samples * sf_get_sample_size(ip_data->sf);
++ }
++ } else {
++ *in += skip_samples * cc->channels * sf_get_sample_size(ip_data->sf);
++ }
++
++ input->seek_ts = -1;
++ input->prev_frame_end = -1;
++ }
++
++ int res = swr_convert(swr,
++ &output->buffer,
++ frame->nb_samples,
++ in,
++ in_count);
++ if (res < 0)
++ res = 0;
++
++ output->buffer_pos = output->buffer;
+ #if LIBAVCODEC_VERSION_MAJOR >= 60
+- output->buffer_used_len = res * cc->ch_layout.nb_channels * sf_get_sample_size(ip_data->sf);
++ output->buffer_used_len = res * cc->ch_layout.nb_channels * sf_get_sample_size(ip_data->sf);
+ #else
+- output->buffer_used_len = res * cc->channels * sf_get_sample_size(ip_data->sf);
++ output->buffer_used_len = res * cc->channels * sf_get_sample_size(ip_data->sf);
+ #endif
++
+ #if LIBAVCODEC_VERSION_MAJOR >= 56
+- av_frame_free(&frame);
++ av_frame_free(&frame);
+ #else
+- avcodec_free_frame(&frame);
++ avcodec_free_frame(&frame);
+ #endif
+- return output->buffer_used_len;
+- }
++ return output->buffer_used_len;
+ }
+ /* This should never get here. */
+ return -IP_ERROR_INTERNAL;
+@@ -437,13 +468,16 @@ static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
+ AVStream *st = priv->input_context->streams[priv->input->stream_index];
+ int ret;
+
+- int64_t pts = av_rescale_q(offset * AV_TIME_BASE, AV_TIME_BASE_Q, st->time_base);
++ priv->input->seek_ts = offset * AV_TIME_BASE;
++ priv->input->prev_frame_end = -1;
++ int64_t ts = av_rescale(offset, st->time_base.den, st->time_base.num);
+
+ avcodec_flush_buffers(priv->codec_context);
+ /* Force reading a new packet in next ffmpeg_fill_buffer(). */
+ priv->input->curr_pkt_size = 0;
+
+- ret = av_seek_frame(priv->input_context, priv->input->stream_index, pts, 0);
++ ret = avformat_seek_file(priv->input_context,
++ priv->input->stream_index, 0, ts, ts, 0);
+
+ if (ret < 0) {
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
--- /dev/null
+From: ihy123 <aladinandreyy@gmail.com>
+Date: Fri, 15 Aug 2025 21:42:19 +0300
+Subject: ip/ffmpeg: skip samples only when needed
+
+---
+ ip/ffmpeg.c | 32 ++++++++++++++++++--------------
+ 1 file changed, 18 insertions(+), 14 deletions(-)
+
+diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
+index ecbf005..5f5a4f3 100644
+--- a/ip/ffmpeg.c
++++ b/ip/ffmpeg.c
+@@ -393,22 +393,26 @@ static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data, AVFormatContext
+ frame_ts = av_rescale_q(frame_ts, st->time_base, AV_TIME_BASE_Q);
+ else
+ frame_ts = input->prev_frame_end;
+- int64_t frame_dur = av_rescale(frame->nb_samples, AV_TIME_BASE, sf_get_rate(ip_data->sf));
+- int64_t frame_end = frame_ts + frame_dur;
+- input->prev_frame_end = frame_end;
+- d_print("seek_ts: %ld, frame_ts: %ld, frame_end: %ld\n", input->seek_ts, frame_ts, frame_end);
+- if (frame_end <= input->seek_ts)
+- continue;
+
+- /* skip part of this frame */
+- int64_t skip_samples = av_rescale(input->seek_ts - frame_ts, sf_get_rate(ip_data->sf), AV_TIME_BASE);
+- in_count -= skip_samples;
+- if (av_sample_fmt_is_planar(frame->format)) {
+- for (int i = 0; i < cc->channels; i++) {
+- in[i] += skip_samples * sf_get_sample_size(ip_data->sf);
++ if (frame_ts < input->seek_ts) {
++ int64_t frame_dur = av_rescale(frame->nb_samples, AV_TIME_BASE, sf_get_rate(ip_data->sf));
++ int64_t frame_end = frame_ts + frame_dur;
++ input->prev_frame_end = frame_end;
++ d_print("seek_ts: %ld, frame_ts: %ld, frame_end: %ld\n", input->seek_ts, frame_ts, frame_end);
++ if (frame_end <= input->seek_ts)
++ continue;
++
++ /* skip part of this frame */
++ int64_t skip_samples = av_rescale(input->seek_ts - frame_ts, sf_get_rate(ip_data->sf), AV_TIME_BASE);
++ in_count -= skip_samples;
++ if (av_sample_fmt_is_planar(frame->format)) {
++ for (int i = 0; i < cc->channels; i++) {
++ in[i] += skip_samples * sf_get_sample_size(ip_data->sf);
++ }
++ } else {
++ *in += skip_samples * cc->channels * sf_get_sample_size(ip_data->sf);
+ }
+- } else {
+- *in += skip_samples * cc->channels * sf_get_sample_size(ip_data->sf);
++ d_print("skipping %ld samples\n", skip_samples);
+ }
+
+ input->seek_ts = -1;
--- /dev/null
+From: ihy123 <aladinandreyy@gmail.com>
+Date: Sat, 16 Aug 2025 02:43:55 +0300
+Subject: ip/ffmpeg: remove excessive version checks
+
+ffmpeg download page states that v4.0.6 has
+- libavutil 56.14.100
+- libavcodec 58.18.100
+- libavformat 58.12.100
+(https://ffmpeg.org/olddownload.html)
+
+After removing all checks for versions lower than these, the plugin
+still compiles with v3.3.9 headers.
+
+After all, why be better with compatibility than developers themselves?
+---
+ ip/ffmpeg.c | 109 +++++++++++++-----------------------------------------------
+ 1 file changed, 23 insertions(+), 86 deletions(-)
+
+diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
+index 5f5a4f3..f6a11f4 100644
+--- a/ip/ffmpeg.c
++++ b/ip/ffmpeg.c
+@@ -25,7 +25,6 @@
+ #include "../config/ffmpeg.h"
+ #endif
+
+-#include <stdio.h>
+ #include <libavcodec/avcodec.h>
+ #include <libavformat/avformat.h>
+ #include <libavformat/avio.h>
+@@ -43,7 +42,6 @@
+ struct ffmpeg_input {
+ AVPacket pkt;
+ int curr_pkt_size;
+- uint8_t *curr_pkt_buf;
+ int64_t seek_ts;
+ int64_t prev_frame_end;
+ int stream_index;
+@@ -80,17 +78,12 @@ static struct ffmpeg_input *ffmpeg_input_create(void)
+ input->curr_pkt_size = 0;
+ input->seek_ts = -1;
+ input->prev_frame_end = -1;
+- input->curr_pkt_buf = input->pkt.data;
+ return input;
+ }
+
+ static void ffmpeg_input_free(struct ffmpeg_input *input)
+ {
+-#if LIBAVCODEC_VERSION_MAJOR >= 56
+ av_packet_unref(&input->pkt);
+-#else
+- av_free_packet(&input->pkt);
+-#endif
+ free(input);
+ }
+
+@@ -132,7 +125,7 @@ static void ffmpeg_init(void)
+
+ av_log_set_level(AV_LOG_QUIET);
+
+-#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 18, 100)
++#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
+ /* We could register decoders explicitly to save memory, but we have to
+ * be careful about compatibility. */
+ av_register_all();
+@@ -149,9 +142,7 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
+ AVCodec const *codec;
+ AVCodecContext *cc = NULL;
+ AVFormatContext *ic = NULL;
+-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
+ AVCodecParameters *cp = NULL;
+-#endif
+ SwrContext *swr = NULL;
+
+ ffmpeg_init();
+@@ -171,20 +162,11 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
+ }
+
+ for (i = 0; i < ic->nb_streams; i++) {
+-
+-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
+ cp = ic->streams[i]->codecpar;
+ if (cp->codec_type == AVMEDIA_TYPE_AUDIO) {
+ stream_index = i;
+ break;
+ }
+-#else
+- cc = ic->streams[i]->codec;
+- if (cc->codec_type == AVMEDIA_TYPE_AUDIO) {
+- stream_index = i;
+- break;
+- }
+-#endif
+ }
+
+ if (stream_index == -1) {
+@@ -193,13 +175,9 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
+ break;
+ }
+
+-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
+ codec = avcodec_find_decoder(cp->codec_id);
+ cc = avcodec_alloc_context3(codec);
+ avcodec_parameters_to_context(cc, cp);
+-#else
+- codec = avcodec_find_decoder(cc->codec_id);
+-#endif
+ if (!codec) {
+ d_print("codec not found: %d, %s\n", cc->codec_id, avcodec_get_name(cc->codec_id));
+ err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
+@@ -217,9 +195,7 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
+
+ if (err < 0) {
+ /* Clean up. cc is never opened at this point. (See above assumption.) */
+-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
+ avcodec_free_context(&cc);
+-#endif
+ avformat_close_input(&ic);
+ return err;
+ }
+@@ -231,9 +207,7 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
+ priv->input = ffmpeg_input_create();
+ if (priv->input == NULL) {
+ avcodec_close(cc);
+-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
+ avcodec_free_context(&cc);
+-#endif
+ avformat_close_input(&ic);
+ free(priv);
+ return -IP_ERROR_INTERNAL;
+@@ -244,7 +218,7 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
+ /* Prepare for resampling. */
+ out_sample_rate = min_u(cc->sample_rate, 384000);
+ swr = swr_alloc();
+-#if LIBAVCODEC_VERSION_MAJOR >= 60
++#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
+ if (cc->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC)
+ av_channel_layout_default(&cc->ch_layout, cc->ch_layout.nb_channels);
+ av_opt_set_chlayout(swr, "in_chlayout", &cc->ch_layout, 0);
+@@ -259,7 +233,7 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
+ priv->swr = swr;
+
+ ip_data->private = priv;
+-#if LIBAVCODEC_VERSION_MAJOR >= 60
++#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
+ ip_data->sf = sf_rate(out_sample_rate) | sf_channels(cc->ch_layout.nb_channels);
+ #else
+ ip_data->sf = sf_rate(out_sample_rate) | sf_channels(cc->channels);
+@@ -281,10 +255,12 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
+ }
+ swr_init(swr);
+ ip_data->sf |= sf_host_endian();
+-#if LIBAVCODEC_VERSION_MAJOR >= 60
+- channel_map_init_waveex(cc->ch_layout.nb_channels, cc->ch_layout.u.mask, ip_data->channel_map);
++#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
++ channel_map_init_waveex(cc->ch_layout.nb_channels,
++ cc->ch_layout.u.mask, ip_data->channel_map);
+ #else
+- channel_map_init_waveex(cc->channels, cc->channel_layout, ip_data->channel_map);
++ channel_map_init_waveex(cc->channels,
++ cc->channel_layout, ip_data->channel_map);
+ #endif
+ return 0;
+ }
+@@ -294,9 +270,7 @@ static int ffmpeg_close(struct input_plugin_data *ip_data)
+ struct ffmpeg_private *priv = ip_data->private;
+
+ avcodec_close(priv->codec_context);
+-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
+ avcodec_free_context(&priv->codec_context);
+-#endif
+ avformat_close_input(&priv->input_context);
+ swr_free(&priv->swr);
+ ffmpeg_input_free(priv->input);
+@@ -310,39 +284,27 @@ static int ffmpeg_close(struct input_plugin_data *ip_data)
+ * This returns the number of bytes added to the buffer.
+ * It returns < 0 on error. 0 on EOF.
+ */
+-static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data, AVFormatContext *ic, AVCodecContext *cc,
+- struct ffmpeg_input *input, struct ffmpeg_output *output, SwrContext *swr)
++static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data,
++ AVFormatContext *ic, AVCodecContext *cc,
++ struct ffmpeg_input *input, struct ffmpeg_output *output,
++ SwrContext *swr)
+ {
+-#if LIBAVCODEC_VERSION_MAJOR >= 56
+ AVFrame *frame = av_frame_alloc();
+-#else
+- AVFrame *frame = avcodec_alloc_frame();
+-#endif
+ while (1) {
+ if (input->curr_pkt_size <= 0) {
+-#if LIBAVCODEC_VERSION_MAJOR >= 56
+ av_packet_unref(&input->pkt);
+-#else
+- av_free_packet(&input->pkt);
+-#endif
+ if (av_read_frame(ic, &input->pkt) < 0) {
+ /* Force EOF once we can read no longer. */
+-#if LIBAVCODEC_VERSION_MAJOR >= 56
+ av_frame_free(&frame);
+-#else
+- avcodec_free_frame(&frame);
+-#endif
+ return 0;
+ }
+
+ if (input->pkt.stream_index != input->stream_index)
+ continue;
+ input->curr_pkt_size = input->pkt.size;
+- input->curr_pkt_buf = input->pkt.data;
+ input->curr_size += input->pkt.size;
+ input->curr_duration += input->pkt.duration;
+
+-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
+ int send_result = avcodec_send_packet(cc, &input->pkt);
+ if (send_result != 0 && send_result != AVERROR(EAGAIN)) {
+ d_print("avcodec_send_packet() returned %d\n", send_result);
+@@ -355,32 +317,17 @@ static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data, AVFormatContext
+ }
+ return -IP_ERROR_INTERNAL;
+ }
+-#endif
+ }
+
+-#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101)
+ int recv_result = avcodec_receive_frame(cc, frame);
+ if (recv_result < 0) {
+ input->curr_pkt_size = 0;
+ continue;
+ }
+-#else
+- int got_frame;
+- int len = avcodec_decode_audio4(cc, frame, &got_frame, &input->pkt);
+- if (len < 0) {
+- /* this is often reached when seeking, not sure why */
+- input->curr_pkt_size = 0;
+- continue;
+- }
+- if (!got_frame)
+- continue;
+-#endif
+
+ int64_t frame_ts = -1;
+ if (frame->pts)
+ frame_ts = frame->pts;
+- else if (frame->pkt_pts)
+- frame_ts = frame->pkt_pts;
+ else if (frame->pkt_dts)
+ frame_ts = frame->pkt_dts;
+
+@@ -395,7 +342,7 @@ static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data, AVFormatContext
+ frame_ts = input->prev_frame_end;
+
+ if (frame_ts < input->seek_ts) {
+- int64_t frame_dur = av_rescale(frame->nb_samples, AV_TIME_BASE, sf_get_rate(ip_data->sf));
++ int64_t frame_dur = av_rescale(frame->nb_samples, AV_TIME_BASE, frame->sample_rate);
+ int64_t frame_end = frame_ts + frame_dur;
+ input->prev_frame_end = frame_end;
+ d_print("seek_ts: %ld, frame_ts: %ld, frame_end: %ld\n", input->seek_ts, frame_ts, frame_end);
+@@ -403,14 +350,14 @@ static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data, AVFormatContext
+ continue;
+
+ /* skip part of this frame */
+- int64_t skip_samples = av_rescale(input->seek_ts - frame_ts, sf_get_rate(ip_data->sf), AV_TIME_BASE);
++ int64_t skip_samples = av_rescale(input->seek_ts - frame_ts, frame->sample_rate, AV_TIME_BASE);
+ in_count -= skip_samples;
+ if (av_sample_fmt_is_planar(frame->format)) {
+- for (int i = 0; i < cc->channels; i++) {
++ for (int i = 0; i < sf_get_channels(ip_data->sf); i++) {
+ in[i] += skip_samples * sf_get_sample_size(ip_data->sf);
+ }
+ } else {
+- *in += skip_samples * cc->channels * sf_get_sample_size(ip_data->sf);
++ *in += skip_samples * sf_get_frame_size(ip_data->sf);
+ }
+ d_print("skipping %ld samples\n", skip_samples);
+ }
+@@ -428,17 +375,9 @@ static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data, AVFormatContext
+ res = 0;
+
+ output->buffer_pos = output->buffer;
+-#if LIBAVCODEC_VERSION_MAJOR >= 60
+- output->buffer_used_len = res * cc->ch_layout.nb_channels * sf_get_sample_size(ip_data->sf);
+-#else
+- output->buffer_used_len = res * cc->channels * sf_get_sample_size(ip_data->sf);
+-#endif
++ output->buffer_used_len = res * sf_get_frame_size(ip_data->sf);
+
+-#if LIBAVCODEC_VERSION_MAJOR >= 56
+ av_frame_free(&frame);
+-#else
+- avcodec_free_frame(&frame);
+-#endif
+ return output->buffer_used_len;
+ }
+ /* This should never get here. */
+@@ -453,11 +392,11 @@ static int ffmpeg_read(struct input_plugin_data *ip_data, char *buffer, int coun
+ int out_size;
+
+ if (output->buffer_used_len == 0) {
+- rc = ffmpeg_fill_buffer(ip_data, priv->input_context, priv->codec_context,
++ rc = ffmpeg_fill_buffer(ip_data,
++ priv->input_context, priv->codec_context,
+ priv->input, priv->output, priv->swr);
+- if (rc <= 0) {
++ if (rc <= 0)
+ return rc;
+- }
+ }
+ out_size = min_i(output->buffer_used_len, count);
+ memcpy(buffer, output->buffer_pos, out_size);
+@@ -477,6 +416,7 @@ static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
+ int64_t ts = av_rescale(offset, st->time_base.den, st->time_base.num);
+
+ avcodec_flush_buffers(priv->codec_context);
++ /* TODO: also flush swresample buffers */
+ /* Force reading a new packet in next ffmpeg_fill_buffer(). */
+ priv->input->curr_pkt_size = 0;
+
+@@ -501,7 +441,8 @@ static void ffmpeg_read_metadata(struct growing_keyvals *c, AVDictionary *metada
+ }
+ }
+
+-static int ffmpeg_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
++static int ffmpeg_read_comments(struct input_plugin_data *ip_data,
++ struct keyval **comments)
+ {
+ struct ffmpeg_private *priv = ip_data->private;
+ AVFormatContext *ic = priv->input_context;
+@@ -538,11 +479,7 @@ static long ffmpeg_current_bitrate(struct input_plugin_data *ip_data)
+ AVStream *st = priv->input_context->streams[priv->input->stream_index];
+ long bitrate = -1;
+ /* ape codec returns silly numbers */
+-#if LIBAVCODEC_VERSION_MAJOR >= 55
+ if (priv->codec->id == AV_CODEC_ID_APE)
+-#else
+- if (priv->codec->id == CODEC_ID_APE)
+-#endif
+ return -1;
+ if (priv->input->curr_duration > 0) {
+ double seconds = priv->input->curr_duration * av_q2d(st->time_base);
--- /dev/null
+From: ihy123 <aladinandreyy@gmail.com>
+Date: Sun, 17 Aug 2025 04:05:36 +0300
+Subject: ip/ffmpeg: major refactor
+
+---
+ ip/ffmpeg.c | 643 +++++++++++++++++++++++++++++++-----------------------------
+ 1 file changed, 330 insertions(+), 313 deletions(-)
+
+diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
+index f6a11f4..42f630e 100644
+--- a/ip/ffmpeg.c
++++ b/ip/ffmpeg.c
+@@ -35,84 +35,32 @@
+ #include <libavutil/mathematics.h>
+ #endif
+
+-#ifndef AVCODEC_MAX_AUDIO_FRAME_SIZE
+-#define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000
+-#endif
++struct ffmpeg_private {
++ AVCodecContext *codec_ctx;
++ AVFormatContext *format_ctx;
++ AVCodec const *codec;
++ SwrContext *swr;
++ int stream_index;
+
+-struct ffmpeg_input {
+- AVPacket pkt;
+- int curr_pkt_size;
++ AVPacket *pkt;
++ AVFrame *frame;
+ int64_t seek_ts;
+ int64_t prev_frame_end;
+- int stream_index;
+
++ /* A buffer to hold swr_convert()-ed samples */
++ AVFrame *swr_frame;
++ int swr_frame_start;
++
++ /* Bitrate estimation */
+ unsigned long curr_size;
+ unsigned long curr_duration;
+ };
+
+-struct ffmpeg_output {
+- uint8_t *buffer;
+- uint8_t *buffer_malloc;
+- uint8_t *buffer_pos; /* current buffer position */
+- int buffer_used_len;
+-};
+-
+-struct ffmpeg_private {
+- AVCodecContext *codec_context;
+- AVFormatContext *input_context;
+- AVCodec const *codec;
+- SwrContext *swr;
+-
+- struct ffmpeg_input *input;
+- struct ffmpeg_output *output;
+-};
+-
+-static struct ffmpeg_input *ffmpeg_input_create(void)
+-{
+- struct ffmpeg_input *input = xnew(struct ffmpeg_input, 1);
+-
+- if (av_new_packet(&input->pkt, 0) != 0) {
+- free(input);
+- return NULL;
+- }
+- input->curr_pkt_size = 0;
+- input->seek_ts = -1;
+- input->prev_frame_end = -1;
+- return input;
+-}
+-
+-static void ffmpeg_input_free(struct ffmpeg_input *input)
+-{
+- av_packet_unref(&input->pkt);
+- free(input);
+-}
+-
+-static struct ffmpeg_output *ffmpeg_output_create(void)
+-{
+- struct ffmpeg_output *output = xnew(struct ffmpeg_output, 1);
+-
+- output->buffer_malloc = xnew(uint8_t, AVCODEC_MAX_AUDIO_FRAME_SIZE + 15);
+- output->buffer = output->buffer_malloc;
+- /* align to 16 bytes so avcodec can SSE/Altivec/etc */
+- while ((intptr_t) output->buffer % 16)
+- output->buffer += 1;
+- output->buffer_pos = output->buffer;
+- output->buffer_used_len = 0;
+- return output;
+-}
+-
+-static void ffmpeg_output_free(struct ffmpeg_output *output)
+-{
+- free(output->buffer_malloc);
+- output->buffer_malloc = NULL;
+- output->buffer = NULL;
+- free(output);
+-}
+-
+-static inline void ffmpeg_buffer_flush(struct ffmpeg_output *output)
++static const char *ffmpeg_errmsg(int err)
+ {
+- output->buffer_pos = output->buffer;
+- output->buffer_used_len = 0;
++ static char errstr[AV_ERROR_MAX_STRING_SIZE];
++ av_strerror(err, errstr, AV_ERROR_MAX_STRING_SIZE);
++ return errstr;
+ }
+
+ static void ffmpeg_init(void)
+@@ -132,303 +80,372 @@ static void ffmpeg_init(void)
+ #endif
+ }
+
+-static int ffmpeg_open(struct input_plugin_data *ip_data)
++static int ffmpeg_open_input(struct input_plugin_data *ip_data,
++ struct ffmpeg_private *priv)
+ {
+- struct ffmpeg_private *priv;
+- int err = 0;
+- int i;
+- int stream_index = -1;
+- int out_sample_rate;
+- AVCodec const *codec;
+- AVCodecContext *cc = NULL;
+ AVFormatContext *ic = NULL;
++ AVCodecContext *cc = NULL;
+ AVCodecParameters *cp = NULL;
+- SwrContext *swr = NULL;
+-
+- ffmpeg_init();
++ AVCodec const *codec = NULL;
++ int stream_index = -1;
+
+- err = avformat_open_input(&ic, ip_data->filename, NULL, NULL);
+- if (err < 0) {
+- d_print("av_open failed: %d\n", err);
+- return -IP_ERROR_FILE_FORMAT;
++ int err;
++ int res = avformat_open_input(&ic, ip_data->filename, NULL, NULL);
++ if (res < 0) {
++ err = -IP_ERROR_FILE_FORMAT;
++ goto err;
+ }
+
+- do {
+- err = avformat_find_stream_info(ic, NULL);
+- if (err < 0) {
+- d_print("unable to find stream info: %d\n", err);
+- err = -IP_ERROR_FILE_FORMAT;
+- break;
+- }
+-
+- for (i = 0; i < ic->nb_streams; i++) {
+- cp = ic->streams[i]->codecpar;
+- if (cp->codec_type == AVMEDIA_TYPE_AUDIO) {
+- stream_index = i;
+- break;
+- }
+- }
+-
+- if (stream_index == -1) {
+- d_print("could not find audio stream\n");
+- err = -IP_ERROR_FILE_FORMAT;
+- break;
+- }
+-
+- codec = avcodec_find_decoder(cp->codec_id);
+- cc = avcodec_alloc_context3(codec);
+- avcodec_parameters_to_context(cc, cp);
+- if (!codec) {
+- d_print("codec not found: %d, %s\n", cc->codec_id, avcodec_get_name(cc->codec_id));
+- err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
+- break;
+- }
++ res = avformat_find_stream_info(ic, NULL);
++ if (res < 0) {
++ d_print("unable to find stream info\n");
++ err = -IP_ERROR_FILE_FORMAT;
++ goto err;
++ }
+
+- if (avcodec_open2(cc, codec, NULL) < 0) {
+- d_print("could not open codec: %d, %s\n", cc->codec_id, avcodec_get_name(cc->codec_id));
+- err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
++ for (int i = 0; i < ic->nb_streams; i++) {
++ cp = ic->streams[i]->codecpar;
++ if (cp->codec_type == AVMEDIA_TYPE_AUDIO) {
++ stream_index = i;
+ break;
+ }
++ }
+
+- /* We assume below that no more errors follow. */
+- } while (0);
++ if (stream_index == -1) {
++ d_print("could not find audio stream\n");
++ err = -IP_ERROR_FILE_FORMAT;
++ goto err_silent;
++ }
+
+- if (err < 0) {
+- /* Clean up. cc is never opened at this point. (See above assumption.) */
+- avcodec_free_context(&cc);
+- avformat_close_input(&ic);
+- return err;
++ codec = avcodec_find_decoder(cp->codec_id);
++ if (!codec) {
++ d_print("codec (id: %d, name: %s) not found\n",
++ cc->codec_id, avcodec_get_name(cc->codec_id));
++ err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
++ goto err_silent;
++ }
++ cc = avcodec_alloc_context3(codec);
++ avcodec_parameters_to_context(cc, cp);
++
++ res = avcodec_open2(cc, codec, NULL);
++ if (res < 0) {
++ d_print("could not open codec (id: %d, name: %s)\n",
++ cc->codec_id, avcodec_get_name(cc->codec_id));
++ err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
++ goto err;
+ }
+
+- priv = xnew(struct ffmpeg_private, 1);
+- priv->codec_context = cc;
+- priv->input_context = ic;
++ priv->format_ctx = ic;
++ priv->codec_ctx = cc;
+ priv->codec = codec;
+- priv->input = ffmpeg_input_create();
+- if (priv->input == NULL) {
+- avcodec_close(cc);
+- avcodec_free_context(&cc);
+- avformat_close_input(&ic);
+- free(priv);
+- return -IP_ERROR_INTERNAL;
++ priv->stream_index = stream_index;
++ return 0;
++err:
++ d_print("%s\n", ffmpeg_errmsg(res));
++err_silent:
++ avcodec_free_context(&cc);
++ avformat_close_input(&ic);
++ return err;
++}
++
++static void ffmpeg_set_sf_and_swr_opts(SwrContext *swr, AVCodecContext *cc,
++ sample_format_t *sf_out, enum AVSampleFormat *out_sample_fmt)
++{
++ int out_sample_rate = min_u(cc->sample_rate, 384000);
++ sample_format_t sf = sf_rate(out_sample_rate) | sf_host_endian();
++ av_opt_set_int(swr, "in_sample_rate", cc->sample_rate, 0);
++ av_opt_set_int(swr, "out_sample_rate", out_sample_rate, 0);
++
++ *out_sample_fmt = cc->sample_fmt;
++ switch (*out_sample_fmt) {
++ case AV_SAMPLE_FMT_U8:
++ sf |= sf_bits(8) | sf_signed(0);
++ break;
++ case AV_SAMPLE_FMT_S32:
++ sf |= sf_bits(32) | sf_signed(1);
++ break;
++ default:
++ sf |= sf_bits(16) | sf_signed(1);
++ *out_sample_fmt = AV_SAMPLE_FMT_S16;
+ }
+- priv->input->stream_index = stream_index;
+- priv->output = ffmpeg_output_create();
++ av_opt_set_sample_fmt(swr, "in_sample_fmt", cc->sample_fmt, 0);
++ av_opt_set_sample_fmt(swr, "out_sample_fmt", *out_sample_fmt, 0);
+
+- /* Prepare for resampling. */
+- out_sample_rate = min_u(cc->sample_rate, 384000);
+- swr = swr_alloc();
+ #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
++ sf |= sf_channels(cc->ch_layout.nb_channels);
++
+ if (cc->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC)
+ av_channel_layout_default(&cc->ch_layout, cc->ch_layout.nb_channels);
+- av_opt_set_chlayout(swr, "in_chlayout", &cc->ch_layout, 0);
+- av_opt_set_chlayout(swr, "out_chlayout", &cc->ch_layout, 0);
++ av_opt_set_chlayout(swr, "in_chlayout", &cc->ch_layout, 0);
++ av_opt_set_chlayout(swr, "out_chlayout", &cc->ch_layout, 0);
+ #else
+- av_opt_set_int(swr, "in_channel_layout", av_get_default_channel_layout(cc->channels), 0);
+- av_opt_set_int(swr, "out_channel_layout", av_get_default_channel_layout(cc->channels), 0);
++ sf |= sf_channels(cc->channels);
++
++ av_opt_set_int(swr, "in_channel_layout",
++ av_get_default_channel_layout(cc->channels), 0);
++ av_opt_set_int(swr, "out_channel_layout",
++ av_get_default_channel_layout(cc->channels), 0);
+ #endif
+- av_opt_set_int(swr, "in_sample_rate", cc->sample_rate, 0);
+- av_opt_set_int(swr, "out_sample_rate", out_sample_rate, 0);
+- av_opt_set_sample_fmt(swr, "in_sample_fmt", cc->sample_fmt, 0);
+- priv->swr = swr;
+
+- ip_data->private = priv;
++ *sf_out = sf;
++}
++
++static int ffmpeg_init_swr_frame(struct ffmpeg_private *priv,
++ sample_format_t sf, enum AVSampleFormat out_sample_fmt)
++{
++ AVCodecContext *cc = priv->codec_ctx;
++ AVFrame *frame = av_frame_alloc();
++
+ #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
+- ip_data->sf = sf_rate(out_sample_rate) | sf_channels(cc->ch_layout.nb_channels);
++ av_channel_layout_copy(&frame->ch_layout, &cc->ch_layout);
+ #else
+- ip_data->sf = sf_rate(out_sample_rate) | sf_channels(cc->channels);
++ frame->channel_layout = av_get_default_channel_layout(cc->channels);
+ #endif
+- switch (cc->sample_fmt) {
+- case AV_SAMPLE_FMT_U8:
+- ip_data->sf |= sf_bits(8) | sf_signed(0);
+- av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_U8, 0);
+- break;
+- case AV_SAMPLE_FMT_S32:
+- ip_data->sf |= sf_bits(32) | sf_signed(1);
+- av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S32, 0);
+- break;
+- /* AV_SAMPLE_FMT_S16 */
+- default:
+- ip_data->sf |= sf_bits(16) | sf_signed(1);
+- av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
+- break;
++
++ frame->sample_rate = sf_get_rate(sf);
++ frame->format = out_sample_fmt;
++
++ /* NOTE: 10 sec is probably too much, but the amount of space
++ * needed for swr_convert() is unpredictable */
++ frame->nb_samples = 10 * sf_get_rate(sf);
++ int res = av_frame_get_buffer(frame, 0);
++ if (res < 0) {
++ d_print("av_frame_get_buffer(): %s\n", ffmpeg_errmsg(res));
++ return -IP_ERROR_INTERNAL;
+ }
+- swr_init(swr);
+- ip_data->sf |= sf_host_endian();
++ frame->nb_samples = 0;
++
++ priv->swr_frame = frame;
++ return 0;
++}
++
++static void ffmpeg_free(struct ffmpeg_private *priv)
++{
++ avcodec_close(priv->codec_ctx);
++ avcodec_free_context(&priv->codec_ctx);
++ avformat_close_input(&priv->format_ctx);
++
++ swr_free(&priv->swr);
++
++ av_frame_free(&priv->frame);
++ av_packet_free(&priv->pkt);
++ av_frame_free(&priv->swr_frame);
++}
++
++static int ffmpeg_open(struct input_plugin_data *ip_data)
++{
++ struct ffmpeg_private priv;
++ enum AVSampleFormat out_sample_fmt;
++ memset(&priv, 0, sizeof(struct ffmpeg_private));
++
++ ffmpeg_init();
++
++ int err = ffmpeg_open_input(ip_data, &priv);
++ if (err < 0)
++ return err;
++
++ priv.pkt = av_packet_alloc();
++ priv.frame = av_frame_alloc();
++ priv.seek_ts = -1;
++ priv.prev_frame_end = -1;
++
++ priv.swr = swr_alloc();
++ ffmpeg_set_sf_and_swr_opts(priv.swr, priv.codec_ctx,
++ &ip_data->sf, &out_sample_fmt);
++ swr_init(priv.swr);
++
++ err = ffmpeg_init_swr_frame(&priv, ip_data->sf, out_sample_fmt);
++ if (err < 0) {
++ ffmpeg_free(&priv);
++ return err;
++ }
++
+ #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
+- channel_map_init_waveex(cc->ch_layout.nb_channels,
+- cc->ch_layout.u.mask, ip_data->channel_map);
++ channel_map_init_waveex(priv.codec_ctx->ch_layout.nb_channels,
++ priv.codec_ctx->ch_layout.u.mask, ip_data->channel_map);
+ #else
+- channel_map_init_waveex(cc->channels,
+- cc->channel_layout, ip_data->channel_map);
++ channel_map_init_waveex(priv.codec_ctx->channels,
++ priv.codec_ctx->channel_layout, ip_data->channel_map);
+ #endif
++
++ ip_data->private = xnew(struct ffmpeg_private, 1);
++ memcpy(ip_data->private, &priv, sizeof(struct ffmpeg_private));
+ return 0;
+ }
+
+ static int ffmpeg_close(struct input_plugin_data *ip_data)
+ {
+- struct ffmpeg_private *priv = ip_data->private;
+-
+- avcodec_close(priv->codec_context);
+- avcodec_free_context(&priv->codec_context);
+- avformat_close_input(&priv->input_context);
+- swr_free(&priv->swr);
+- ffmpeg_input_free(priv->input);
+- ffmpeg_output_free(priv->output);
+- free(priv);
++ ffmpeg_free(ip_data->private);
++ free(ip_data->private);
+ ip_data->private = NULL;
+ return 0;
+ }
+
+ /*
+- * This returns the number of bytes added to the buffer.
+- * It returns < 0 on error. 0 on EOF.
++ * return:
++ * 0 - retry
++ * >0 - ok
+ */
+-static int ffmpeg_fill_buffer(struct input_plugin_data *ip_data,
+- AVFormatContext *ic, AVCodecContext *cc,
+- struct ffmpeg_input *input, struct ffmpeg_output *output,
+- SwrContext *swr)
++static int ffmpeg_seek_into_frame(struct ffmpeg_private *priv, int64_t frame_ts)
+ {
+- AVFrame *frame = av_frame_alloc();
+- while (1) {
+- if (input->curr_pkt_size <= 0) {
+- av_packet_unref(&input->pkt);
+- if (av_read_frame(ic, &input->pkt) < 0) {
+- /* Force EOF once we can read no longer. */
+- av_frame_free(&frame);
+- return 0;
+- }
+-
+- if (input->pkt.stream_index != input->stream_index)
+- continue;
+- input->curr_pkt_size = input->pkt.size;
+- input->curr_size += input->pkt.size;
+- input->curr_duration += input->pkt.duration;
+-
+- int send_result = avcodec_send_packet(cc, &input->pkt);
+- if (send_result != 0 && send_result != AVERROR(EAGAIN)) {
+- d_print("avcodec_send_packet() returned %d\n", send_result);
+- char errstr[AV_ERROR_MAX_STRING_SIZE];
+- if (!av_strerror(send_result, errstr, AV_ERROR_MAX_STRING_SIZE ))
+- {
+- d_print("av_strerror(): %s\n", errstr);
+- } else {
+- d_print("av_strerror(): Description for error cannot be found\n");
+- }
+- return -IP_ERROR_INTERNAL;
+- }
+- }
++ if (frame_ts >= 0) {
++ AVStream *s = priv->format_ctx->streams[priv->stream_index];
++ frame_ts = av_rescale_q(frame_ts, s->time_base, AV_TIME_BASE_Q);
++ } else {
++ frame_ts = priv->prev_frame_end;
++ }
+
+- int recv_result = avcodec_receive_frame(cc, frame);
+- if (recv_result < 0) {
+- input->curr_pkt_size = 0;
+- continue;
+- }
++ if (frame_ts >= priv->seek_ts)
++ return 1;
+
+- int64_t frame_ts = -1;
+- if (frame->pts)
+- frame_ts = frame->pts;
+- else if (frame->pkt_dts)
+- frame_ts = frame->pkt_dts;
+-
+- const uint8_t **in = (const uint8_t **)frame->extended_data;
+- int in_count = frame->nb_samples;
+- if (input->seek_ts > 0 && (frame_ts >= 0 || input->prev_frame_end >= 0)) {
+- struct ffmpeg_private *priv = ip_data->private;
+- AVStream *st = priv->input_context->streams[priv->input->stream_index];
+- if (frame_ts >= 0)
+- frame_ts = av_rescale_q(frame_ts, st->time_base, AV_TIME_BASE_Q);
+- else
+- frame_ts = input->prev_frame_end;
+-
+- if (frame_ts < input->seek_ts) {
+- int64_t frame_dur = av_rescale(frame->nb_samples, AV_TIME_BASE, frame->sample_rate);
+- int64_t frame_end = frame_ts + frame_dur;
+- input->prev_frame_end = frame_end;
+- d_print("seek_ts: %ld, frame_ts: %ld, frame_end: %ld\n", input->seek_ts, frame_ts, frame_end);
+- if (frame_end <= input->seek_ts)
+- continue;
+-
+- /* skip part of this frame */
+- int64_t skip_samples = av_rescale(input->seek_ts - frame_ts, frame->sample_rate, AV_TIME_BASE);
+- in_count -= skip_samples;
+- if (av_sample_fmt_is_planar(frame->format)) {
+- for (int i = 0; i < sf_get_channels(ip_data->sf); i++) {
+- in[i] += skip_samples * sf_get_sample_size(ip_data->sf);
+- }
+- } else {
+- *in += skip_samples * sf_get_frame_size(ip_data->sf);
+- }
+- d_print("skipping %ld samples\n", skip_samples);
+- }
+-
+- input->seek_ts = -1;
+- input->prev_frame_end = -1;
+- }
++ int64_t frame_dur = av_rescale(priv->frame->nb_samples,
++ AV_TIME_BASE, priv->frame->sample_rate);
++ int64_t frame_end = frame_ts + frame_dur;
++ priv->prev_frame_end = frame_end;
++
++ d_print("seek_ts: %ld, frame_ts: %ld, frame_end: %ld\n",
++ priv->seek_ts, frame_ts, frame_end);
++
++ if (frame_end <= priv->seek_ts)
++ return 0;
++
++ int64_t skip_samples = av_rescale(priv->seek_ts - frame_ts,
++ priv->frame->sample_rate, AV_TIME_BASE);
++ priv->frame->nb_samples -= skip_samples;
++
++ int bps = av_get_bytes_per_sample(priv->frame->format);
++#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
++ int channels = priv->codec_ctx->ch_layout.nb_channels;
++#else
++ int channels = priv->codec_ctx->channels;
++#endif
++
++ /* Just modify frame's data pointer because it's throw-away */
++ if (av_sample_fmt_is_planar(priv->frame->format)) {
++ for (int i = 0; i < channels; i++)
++ priv->frame->extended_data[i] += skip_samples * bps;
++ } else {
++ priv->frame->extended_data[0] += skip_samples * channels * bps;
++ }
++ d_print("skipping %ld samples\n", skip_samples);
++ return 1;
++}
+
+- int res = swr_convert(swr,
+- &output->buffer,
+- frame->nb_samples,
+- in,
+- in_count);
++/*
++ * return:
++ * <0 - error
++ * 0 - retry
++ * >0 - ok
++ */
++static int ffmpeg_get_frame(struct ffmpeg_private *priv)
++{
++ int res = avcodec_receive_frame(priv->codec_ctx, priv->frame);
++ if (res == AVERROR(EAGAIN)) {
++ av_packet_unref(priv->pkt);
++ res = av_read_frame(priv->format_ctx, priv->pkt);
+ if (res < 0)
+- res = 0;
++ return res;
++
++ if (priv->pkt->stream_index != priv->stream_index)
++ return 0;
+
+- output->buffer_pos = output->buffer;
+- output->buffer_used_len = res * sf_get_frame_size(ip_data->sf);
++ priv->curr_size += priv->pkt->size;
++ priv->curr_duration += priv->pkt->duration;
+
+- av_frame_free(&frame);
+- return output->buffer_used_len;
++ res = avcodec_send_packet(priv->codec_ctx, priv->pkt);
++ if (res == AVERROR(EAGAIN))
++ return 0;
+ }
+- /* This should never get here. */
+- return -IP_ERROR_INTERNAL;
++ if (res < 0)
++ return res;
++
++ int64_t frame_ts = -1;
++ if (priv->frame->pts >= 0)
++ frame_ts = priv->frame->pts;
++ else if (priv->frame->pkt_dts >= 0)
++ frame_ts = priv->frame->pkt_dts;
++
++ if (priv->seek_ts > 0 && (frame_ts >= 0 || priv->prev_frame_end >= 0)) {
++ if (ffmpeg_seek_into_frame(priv, frame_ts) == 0)
++ return 0;
++ priv->seek_ts = -1;
++ priv->prev_frame_end = -1;
++ }
++ return 1;
++}
++
++static int ffmpeg_convert_frame(struct ffmpeg_private *priv)
++{
++ int res = swr_convert(priv->swr,
++ priv->swr_frame->extended_data,
++ /* TODO: proper buffer capacity */
++ priv->frame->nb_samples,
++ (const uint8_t **)priv->frame->extended_data,
++ priv->frame->nb_samples);
++ if (res >= 0) {
++ priv->swr_frame->nb_samples = res;
++ priv->swr_frame_start = 0;
++ }
++ return res;
+ }
+
+ static int ffmpeg_read(struct input_plugin_data *ip_data, char *buffer, int count)
+ {
+ struct ffmpeg_private *priv = ip_data->private;
+- struct ffmpeg_output *output = priv->output;
+- int rc;
+- int out_size;
+-
+- if (output->buffer_used_len == 0) {
+- rc = ffmpeg_fill_buffer(ip_data,
+- priv->input_context, priv->codec_context,
+- priv->input, priv->output, priv->swr);
+- if (rc <= 0)
+- return rc;
++ int written = 0;
++ int res;
++
++ count /= sf_get_frame_size(ip_data->sf);
++
++ while (count) {
++ if (priv->swr_frame->nb_samples == 0) {
++ res = ffmpeg_get_frame(priv);
++ if (res == AVERROR_EOF)
++ break;
++ else if (res == 0)
++ continue;
++ else if (res < 0)
++ goto err;
++
++ res = ffmpeg_convert_frame(priv);
++ if (res < 0)
++ goto err;
++ }
++
++ int copy_frames = min_i(count, priv->swr_frame->nb_samples);
++ int copy_bytes = copy_frames * sf_get_frame_size(ip_data->sf);
++ void *dst = priv->swr_frame->extended_data[0] + priv->swr_frame_start;
++ memcpy(buffer + written, dst, copy_bytes);
++
++ priv->swr_frame->nb_samples -= copy_frames;
++ priv->swr_frame_start += copy_bytes;
++ count -= copy_frames;
++ written += copy_bytes;
+ }
+- out_size = min_i(output->buffer_used_len, count);
+- memcpy(buffer, output->buffer_pos, out_size);
+- output->buffer_used_len -= out_size;
+- output->buffer_pos += out_size;
+- return out_size;
++ return written;
++err:
++ d_print("%s\n", ffmpeg_errmsg(res));
++ return -IP_ERROR_INTERNAL;
+ }
+
+ static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
+ {
+ struct ffmpeg_private *priv = ip_data->private;
+- AVStream *st = priv->input_context->streams[priv->input->stream_index];
+- int ret;
++ AVStream *st = priv->format_ctx->streams[priv->stream_index];
+
+- priv->input->seek_ts = offset * AV_TIME_BASE;
+- priv->input->prev_frame_end = -1;
++ priv->seek_ts = offset * AV_TIME_BASE;
++ priv->prev_frame_end = -1;
+ int64_t ts = av_rescale(offset, st->time_base.den, st->time_base.num);
+
+- avcodec_flush_buffers(priv->codec_context);
+- /* TODO: also flush swresample buffers */
+- /* Force reading a new packet in next ffmpeg_fill_buffer(). */
+- priv->input->curr_pkt_size = 0;
+-
+- ret = avformat_seek_file(priv->input_context,
+- priv->input->stream_index, 0, ts, ts, 0);
+-
+- if (ret < 0) {
++ int ret = avformat_seek_file(priv->format_ctx,
++ priv->stream_index, 0, ts, ts, 0);
++ if (ret < 0)
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+- } else {
+- ffmpeg_buffer_flush(priv->output);
+- return 0;
+- }
++
++ priv->swr_frame->nb_samples = 0;
++ avcodec_flush_buffers(priv->codec_ctx);
++ /* also flush swresample buffers? */
++ return 0;
+ }
+
+ static void ffmpeg_read_metadata(struct growing_keyvals *c, AVDictionary *metadata)
+@@ -445,7 +462,7 @@ static int ffmpeg_read_comments(struct input_plugin_data *ip_data,
+ struct keyval **comments)
+ {
+ struct ffmpeg_private *priv = ip_data->private;
+- AVFormatContext *ic = priv->input_context;
++ AVFormatContext *ic = priv->format_ctx;
+
+ GROWING_KEYVALS(c);
+
+@@ -463,29 +480,29 @@ static int ffmpeg_read_comments(struct input_plugin_data *ip_data,
+ static int ffmpeg_duration(struct input_plugin_data *ip_data)
+ {
+ struct ffmpeg_private *priv = ip_data->private;
+- return priv->input_context->duration / AV_TIME_BASE;
++ return priv->format_ctx->duration / AV_TIME_BASE;
+ }
+
+ static long ffmpeg_bitrate(struct input_plugin_data *ip_data)
+ {
+ struct ffmpeg_private *priv = ip_data->private;
+- long bitrate = priv->input_context->bit_rate;
++ long bitrate = priv->format_ctx->bit_rate;
+ return bitrate ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+ }
+
+ static long ffmpeg_current_bitrate(struct input_plugin_data *ip_data)
+ {
+ struct ffmpeg_private *priv = ip_data->private;
+- AVStream *st = priv->input_context->streams[priv->input->stream_index];
++ AVStream *st = priv->format_ctx->streams[priv->stream_index];
+ long bitrate = -1;
+ /* ape codec returns silly numbers */
+ if (priv->codec->id == AV_CODEC_ID_APE)
+ return -1;
+- if (priv->input->curr_duration > 0) {
+- double seconds = priv->input->curr_duration * av_q2d(st->time_base);
+- bitrate = (8 * priv->input->curr_size) / seconds;
+- priv->input->curr_size = 0;
+- priv->input->curr_duration = 0;
++ if (priv->curr_duration > 0) {
++ double seconds = priv->curr_duration * av_q2d(st->time_base);
++ bitrate = (8 * priv->curr_size) / seconds;
++ priv->curr_size = 0;
++ priv->curr_duration = 0;
+ }
+ return bitrate;
+ }
+@@ -500,7 +517,7 @@ static char *ffmpeg_codec_profile(struct input_plugin_data *ip_data)
+ {
+ struct ffmpeg_private *priv = ip_data->private;
+ const char *profile;
+- profile = av_get_profile_name(priv->codec, priv->codec_context->profile);
++ profile = av_get_profile_name(priv->codec, priv->codec_ctx->profile);
+ return profile ? xstrdup(profile) : NULL;
+ }
+
--- /dev/null
+From: ihy123 <aladinandreyy@gmail.com>
+Date: Sun, 17 Aug 2025 14:28:46 +0300
+Subject: Validate sample format in ip_open()
+
+To prevent segfault in ip_setup() because channels=0, validate ip_data->sf
+after opening ip.
+---
+ input.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+diff --git a/input.c b/input.c
+index c20cb3f..f5c5b3c 100644
+--- a/input.c
++++ b/input.c
+@@ -605,6 +605,16 @@ int ip_open(struct input_plugin *ip)
+ ip_reset(ip, 1);
+ return rc;
+ }
++
++ unsigned bits = sf_get_bits(ip->data.sf);
++ unsigned channels = sf_get_channels(ip->data.sf);
++ unsigned rate = sf_get_rate(ip->data.sf);
++ if (!bits || !channels || !rate) {
++ d_print("corrupt file: bits = %u, channels = %u, rate = %u\n",
++ bits, channels, rate);
++ return -IP_ERROR_FILE_FORMAT;
++ }
++
+ ip->open = 1;
+ return 0;
+ }
--- /dev/null
+From: ihy123 <aladinandreyy@gmail.com>
+Date: Sun, 17 Aug 2025 14:53:52 +0300
+Subject: ip/ffmpeg: flush swresample buffer when seeking
+
+---
+ ip/ffmpeg.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
+index 42f630e..775e7de 100644
+--- a/ip/ffmpeg.c
++++ b/ip/ffmpeg.c
+@@ -444,7 +444,7 @@ static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
+
+ priv->swr_frame->nb_samples = 0;
+ avcodec_flush_buffers(priv->codec_ctx);
+- /* also flush swresample buffers? */
++ swr_convert(priv->swr, NULL, 0, NULL, 0); /* flush swr buffer */
+ return 0;
+ }
+
--- /dev/null
+From: ihy123 <aladinandreyy@gmail.com>
+Date: Sun, 17 Aug 2025 15:02:34 +0300
+Subject: ip/ffmpeg: remember swr_frame's capacity
+
+---
+ ip/ffmpeg.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
+index 775e7de..c659c13 100644
+--- a/ip/ffmpeg.c
++++ b/ip/ffmpeg.c
+@@ -49,6 +49,7 @@ struct ffmpeg_private {
+
+ /* A buffer to hold swr_convert()-ed samples */
+ AVFrame *swr_frame;
++ int swr_frame_samples_cap;
+ int swr_frame_start;
+
+ /* Bitrate estimation */
+@@ -213,6 +214,7 @@ static int ffmpeg_init_swr_frame(struct ffmpeg_private *priv,
+ d_print("av_frame_get_buffer(): %s\n", ffmpeg_errmsg(res));
+ return -IP_ERROR_INTERNAL;
+ }
++ priv->swr_frame_samples_cap = frame->nb_samples;
+ frame->nb_samples = 0;
+
+ priv->swr_frame = frame;
+@@ -378,8 +380,7 @@ static int ffmpeg_convert_frame(struct ffmpeg_private *priv)
+ {
+ int res = swr_convert(priv->swr,
+ priv->swr_frame->extended_data,
+- /* TODO: proper buffer capacity */
+- priv->frame->nb_samples,
++ priv->swr_frame_samples_cap,
+ (const uint8_t **)priv->frame->extended_data,
+ priv->frame->nb_samples);
+ if (res >= 0) {
--- /dev/null
+From: ihy123 <aladinandreyy@gmail.com>
+Date: Sun, 17 Aug 2025 15:54:19 +0300
+Subject: ip/ffmpeg: reset swr_frame_start when seeking
+
+---
+ ip/ffmpeg.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
+index c659c13..71cc511 100644
+--- a/ip/ffmpeg.c
++++ b/ip/ffmpeg.c
+@@ -444,6 +444,7 @@ static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+
+ priv->swr_frame->nb_samples = 0;
++ priv->swr_frame_start = 0;
+ avcodec_flush_buffers(priv->codec_ctx);
+ swr_convert(priv->swr, NULL, 0, NULL, 0); /* flush swr buffer */
+ return 0;
--- /dev/null
+From: ihy123 <aladinandreyy@gmail.com>
+Date: Sun, 17 Aug 2025 17:27:20 +0300
+Subject: ip/ffmpeg: better frame skipping logic
+
+---
+ ip/ffmpeg.c | 82 ++++++++++++++++++++++++++++++-------------------------------
+ 1 file changed, 41 insertions(+), 41 deletions(-)
+
+diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
+index 71cc511..af6ecfb 100644
+--- a/ip/ffmpeg.c
++++ b/ip/ffmpeg.c
+@@ -44,8 +44,8 @@ struct ffmpeg_private {
+
+ AVPacket *pkt;
+ AVFrame *frame;
+- int64_t seek_ts;
+- int64_t prev_frame_end;
++ double seek_ts;
++ int64_t skip_samples;
+
+ /* A buffer to hold swr_convert()-ed samples */
+ AVFrame *swr_frame;
+@@ -249,7 +249,6 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
+ priv.pkt = av_packet_alloc();
+ priv.frame = av_frame_alloc();
+ priv.seek_ts = -1;
+- priv.prev_frame_end = -1;
+
+ priv.swr = swr_alloc();
+ ffmpeg_set_sf_and_swr_opts(priv.swr, priv.codec_ctx,
+@@ -283,37 +282,37 @@ static int ffmpeg_close(struct input_plugin_data *ip_data)
+ return 0;
+ }
+
+-/*
+- * return:
+- * 0 - retry
+- * >0 - ok
+- */
+-static int ffmpeg_seek_into_frame(struct ffmpeg_private *priv, int64_t frame_ts)
++static int64_t ffmpeg_calc_skip_samples(struct ffmpeg_private *priv)
+ {
+- if (frame_ts >= 0) {
+- AVStream *s = priv->format_ctx->streams[priv->stream_index];
+- frame_ts = av_rescale_q(frame_ts, s->time_base, AV_TIME_BASE_Q);
++ int64_t ts;
++ if (priv->frame->pts >= 0) {
++ ts = priv->frame->pts;
++ } else if (priv->frame->pkt_dts >= 0) {
++ ts = priv->frame->pkt_dts;
+ } else {
+- frame_ts = priv->prev_frame_end;
++ d_print("AVFrame.pts and AVFrame.pkt_dts are unset\n");
++ return -1;
+ }
+
+- if (frame_ts >= priv->seek_ts)
+- return 1;
+-
+- int64_t frame_dur = av_rescale(priv->frame->nb_samples,
+- AV_TIME_BASE, priv->frame->sample_rate);
+- int64_t frame_end = frame_ts + frame_dur;
+- priv->prev_frame_end = frame_end;
++ AVStream *s = priv->format_ctx->streams[priv->stream_index];
++ double frame_ts = ts * av_q2d(s->time_base);
+
+- d_print("seek_ts: %ld, frame_ts: %ld, frame_end: %ld\n",
+- priv->seek_ts, frame_ts, frame_end);
++ d_print("seek_ts: %.6fs, frame_ts: %.6fs\n", priv->seek_ts, frame_ts);
+
+- if (frame_end <= priv->seek_ts)
++ if (frame_ts >= priv->seek_ts)
+ return 0;
++ return (priv->seek_ts - frame_ts) * priv->frame->sample_rate;
++}
+
+- int64_t skip_samples = av_rescale(priv->seek_ts - frame_ts,
+- priv->frame->sample_rate, AV_TIME_BASE);
+- priv->frame->nb_samples -= skip_samples;
++static void ffmpeg_skip_frame_part(struct ffmpeg_private *priv)
++{
++ if (priv->skip_samples >= priv->frame->nb_samples) {
++ d_print("skipping frame: %d samples\n",
++ priv->frame->nb_samples);
++ priv->skip_samples -= priv->frame->nb_samples;
++ priv->frame->nb_samples = 0;
++ return;
++ }
+
+ int bps = av_get_bytes_per_sample(priv->frame->format);
+ #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
+@@ -322,15 +321,17 @@ static int ffmpeg_seek_into_frame(struct ffmpeg_private *priv, int64_t frame_ts)
+ int channels = priv->codec_ctx->channels;
+ #endif
+
++ priv->frame->nb_samples -= priv->skip_samples;
++
+ /* Just modify frame's data pointer because it's throw-away */
+ if (av_sample_fmt_is_planar(priv->frame->format)) {
+ for (int i = 0; i < channels; i++)
+- priv->frame->extended_data[i] += skip_samples * bps;
++ priv->frame->extended_data[i] += priv->skip_samples * bps;
+ } else {
+- priv->frame->extended_data[0] += skip_samples * channels * bps;
++ priv->frame->extended_data[0] += priv->skip_samples * channels * bps;
+ }
+- d_print("skipping %ld samples\n", skip_samples);
+- return 1;
++ d_print("skipping %ld samples\n", priv->skip_samples);
++ priv->skip_samples = 0;
+ }
+
+ /*
+@@ -361,17 +362,16 @@ static int ffmpeg_get_frame(struct ffmpeg_private *priv)
+ if (res < 0)
+ return res;
+
+- int64_t frame_ts = -1;
+- if (priv->frame->pts >= 0)
+- frame_ts = priv->frame->pts;
+- else if (priv->frame->pkt_dts >= 0)
+- frame_ts = priv->frame->pkt_dts;
++ if (priv->seek_ts > 0) {
++ priv->skip_samples = ffmpeg_calc_skip_samples(priv);
++ if (priv->skip_samples >= 0)
++ priv->seek_ts = -1;
++ }
+
+- if (priv->seek_ts > 0 && (frame_ts >= 0 || priv->prev_frame_end >= 0)) {
+- if (ffmpeg_seek_into_frame(priv, frame_ts) == 0)
++ if (priv->skip_samples > 0) {
++ ffmpeg_skip_frame_part(priv);
++ if (priv->frame->nb_samples == 0)
+ return 0;
+- priv->seek_ts = -1;
+- priv->prev_frame_end = -1;
+ }
+ return 1;
+ }
+@@ -434,8 +434,8 @@ static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
+ struct ffmpeg_private *priv = ip_data->private;
+ AVStream *st = priv->format_ctx->streams[priv->stream_index];
+
+- priv->seek_ts = offset * AV_TIME_BASE;
+- priv->prev_frame_end = -1;
++ priv->seek_ts = offset;
++ priv->skip_samples = 0;
+ int64_t ts = av_rescale(offset, st->time_base.den, st->time_base.num);
+
+ int ret = avformat_seek_file(priv->format_ctx,
--- /dev/null
+From: ihy123 <aladinandreyy@gmail.com>
+Date: Sun, 17 Aug 2025 19:22:50 +0300
+Subject: ip/ffmpeg: don't process empty frames
+
+---
+ ip/ffmpeg.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
+index af6ecfb..dd9061a 100644
+--- a/ip/ffmpeg.c
++++ b/ip/ffmpeg.c
+@@ -356,7 +356,7 @@ static int ffmpeg_get_frame(struct ffmpeg_private *priv)
+ priv->curr_duration += priv->pkt->duration;
+
+ res = avcodec_send_packet(priv->codec_ctx, priv->pkt);
+- if (res == AVERROR(EAGAIN))
++ if (res == 0 || res == AVERROR(EAGAIN))
+ return 0;
+ }
+ if (res < 0)
--- /dev/null
+From: ihy123 <aladinandreyy@gmail.com>
+Date: Mon, 18 Aug 2025 03:32:22 +0300
+Subject: ip/ffmpeg: improve readability
+
+Previously ffmpeg_read()'s while loop was kinda leaking into
+ffmpeg_get_frame(), now it doesn't.
+---
+ ip/ffmpeg.c | 36 ++++++++++++++++++++----------------
+ 1 file changed, 20 insertions(+), 16 deletions(-)
+
+diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
+index dd9061a..fc74895 100644
+--- a/ip/ffmpeg.c
++++ b/ip/ffmpeg.c
+@@ -337,30 +337,32 @@ static void ffmpeg_skip_frame_part(struct ffmpeg_private *priv)
+ /*
+ * return:
+ * <0 - error
+- * 0 - retry
++ * 0 - eof
+ * >0 - ok
+ */
+ static int ffmpeg_get_frame(struct ffmpeg_private *priv)
+ {
+- int res = avcodec_receive_frame(priv->codec_ctx, priv->frame);
++ int res;
++retry:
++ res = avcodec_receive_frame(priv->codec_ctx, priv->frame);
+ if (res == AVERROR(EAGAIN)) {
+ av_packet_unref(priv->pkt);
+ res = av_read_frame(priv->format_ctx, priv->pkt);
+ if (res < 0)
+- return res;
++ goto err;
+
+ if (priv->pkt->stream_index != priv->stream_index)
+- return 0;
++ goto retry;
+
+ priv->curr_size += priv->pkt->size;
+ priv->curr_duration += priv->pkt->duration;
+
+ res = avcodec_send_packet(priv->codec_ctx, priv->pkt);
+ if (res == 0 || res == AVERROR(EAGAIN))
+- return 0;
++ goto retry;
+ }
+ if (res < 0)
+- return res;
++ goto err;
+
+ if (priv->seek_ts > 0) {
+ priv->skip_samples = ffmpeg_calc_skip_samples(priv);
+@@ -371,9 +373,14 @@ static int ffmpeg_get_frame(struct ffmpeg_private *priv)
+ if (priv->skip_samples > 0) {
+ ffmpeg_skip_frame_part(priv);
+ if (priv->frame->nb_samples == 0)
+- return 0;
++ goto retry;
+ }
+ return 1;
++err:
++ if (res == AVERROR_EOF)
++ return 0;
++ d_print("%s\n", ffmpeg_errmsg(res));
++ return -IP_ERROR_INTERNAL;
+ }
+
+ static int ffmpeg_convert_frame(struct ffmpeg_private *priv)
+@@ -386,8 +393,10 @@ static int ffmpeg_convert_frame(struct ffmpeg_private *priv)
+ if (res >= 0) {
+ priv->swr_frame->nb_samples = res;
+ priv->swr_frame_start = 0;
++ return res;
+ }
+- return res;
++ d_print("%s\n", ffmpeg_errmsg(res));
++ return -IP_ERROR_INTERNAL;
+ }
+
+ static int ffmpeg_read(struct input_plugin_data *ip_data, char *buffer, int count)
+@@ -401,16 +410,14 @@ static int ffmpeg_read(struct input_plugin_data *ip_data, char *buffer, int coun
+ while (count) {
+ if (priv->swr_frame->nb_samples == 0) {
+ res = ffmpeg_get_frame(priv);
+- if (res == AVERROR_EOF)
++ if (res == 0)
+ break;
+- else if (res == 0)
+- continue;
+ else if (res < 0)
+- goto err;
++ return res;
+
+ res = ffmpeg_convert_frame(priv);
+ if (res < 0)
+- goto err;
++ return res;
+ }
+
+ int copy_frames = min_i(count, priv->swr_frame->nb_samples);
+@@ -424,9 +431,6 @@ static int ffmpeg_read(struct input_plugin_data *ip_data, char *buffer, int coun
+ written += copy_bytes;
+ }
+ return written;
+-err:
+- d_print("%s\n", ffmpeg_errmsg(res));
+- return -IP_ERROR_INTERNAL;
+ }
+
+ static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
--- /dev/null
+From: ihy123 <aladinandreyy@gmail.com>
+Date: Sun, 24 Aug 2025 19:16:57 +0300
+Subject: ip/ffmpeg: fix building for ffmpeg 8.0
+
+avcodec_close() can be safely removed because avcodec_free_context()
+is its replacement since 2016. See ffmpeg commit 2ef6dab0a79
+
+Builds with v3.3.9 v4.0.6 v6.1.3 v7.1.1 v8.0
+---
+ ip/ffmpeg.c | 1 -
+ 1 file changed, 1 deletion(-)
+
+diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
+index fc74895..2cb0767 100644
+--- a/ip/ffmpeg.c
++++ b/ip/ffmpeg.c
+@@ -223,7 +223,6 @@ static int ffmpeg_init_swr_frame(struct ffmpeg_private *priv,
+
+ static void ffmpeg_free(struct ffmpeg_private *priv)
+ {
+- avcodec_close(priv->codec_ctx);
+ avcodec_free_context(&priv->codec_ctx);
+ avformat_close_input(&priv->format_ctx);
+
--- /dev/null
+From: ihy123 <aladinandreyy@gmail.com>
+Date: Mon, 25 Aug 2025 11:17:06 +0300
+Subject: ip/ffmpeg: change sample format conversions
+
+---
+ ip/ffmpeg.c | 10 ++++------
+ 1 file changed, 4 insertions(+), 6 deletions(-)
+
+diff --git a/ip/ffmpeg.c b/ip/ffmpeg.c
+index 2cb0767..2d3c610 100644
+--- a/ip/ffmpeg.c
++++ b/ip/ffmpeg.c
+@@ -157,13 +157,11 @@ static void ffmpeg_set_sf_and_swr_opts(SwrContext *swr, AVCodecContext *cc,
+ av_opt_set_int(swr, "in_sample_rate", cc->sample_rate, 0);
+ av_opt_set_int(swr, "out_sample_rate", out_sample_rate, 0);
+
+- *out_sample_fmt = cc->sample_fmt;
+- switch (*out_sample_fmt) {
+- case AV_SAMPLE_FMT_U8:
+- sf |= sf_bits(8) | sf_signed(0);
+- break;
+- case AV_SAMPLE_FMT_S32:
++ switch (cc->sample_fmt) {
++ case AV_SAMPLE_FMT_FLT: case AV_SAMPLE_FMT_FLTP:
++ case AV_SAMPLE_FMT_S32: case AV_SAMPLE_FMT_S32P:
+ sf |= sf_bits(32) | sf_signed(1);
++ *out_sample_fmt = AV_SAMPLE_FMT_S32;
+ break;
+ default:
+ sf |= sf_bits(16) | sf_signed(1);
--- /dev/null
+0001-atomic_ld.patch
+0002-fix-blhc.patch
+0003-ip-ffmpeg-more-precise-seeking.patch
+0004-ip-ffmpeg-skip-samples-only-when-needed.patch
+0005-ip-ffmpeg-remove-excessive-version-checks.patch
+0006-ip-ffmpeg-major-refactor.patch
+0007-Validate-sample-format-in-ip_open.patch
+0008-ip-ffmpeg-flush-swresample-buffer-when-seeking.patch
+0009-ip-ffmpeg-remember-swr_frame-s-capacity.patch
+0010-ip-ffmpeg-reset-swr_frame_start-when-seeking.patch
+0011-ip-ffmpeg-better-frame-skipping-logic.patch
+0012-ip-ffmpeg-don-t-process-empty-frames.patch
+0013-ip-ffmpeg-improve-readability.patch
+0014-ip-ffmpeg-fix-building-for-ffmpeg-8.0.patch
+0015-ip-ffmpeg-change-sample-format-conversions.patch
--- /dev/null
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+include /usr/share/dpkg/architecture.mk
+ifneq ($(DEB_BUILD_ARCH),$(DEB_HOST_ARCH))
+CROSS = CROSS=$(DEB_HOST_GNU_TYPE)- PKG_CONFIG=$(DEB_HOST_GNU_TYPE)-pkg-config
+endif
+
+# The following architectures need the -latomic flag
+# to build, otherwise we FTBFS with
+# ./track_info.c:47: undefined reference to `__atomic_fetch_add_8'
+# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=935678
+# Depends on 13-atomic_ld.patch
+ifneq (,$(findstring $(DEB_HOST_ARCH), armel m68k mipsel powerpc sh4))
+export LDLIBS += -latomic
+endif
+
+DEB_CFLAGS_MAINT_APPEND += -I/usr/include/ncursesw
+DEB_BUILD_MAINT_OPTIONS = hardening=+all
+DPKG_EXPORT_BUILDFLAGS = 1
+include /usr/share/dpkg/buildflags.mk
+
+suggested_deps = pulse jack
+
+EXTRA_CMUS_DIR_OP_PLUGINS = debian/cmus/usr/lib/cmus/op/
+EXTRA_CMUS_PLUGINS := $(foreach plugin,$(suggested_deps),$(plugin).so)
+
+%:
+ dh $@ --with bash-completion
+
+override_dh_auto_configure:
+ $(CROSS) ./configure \
+ prefix=/usr \
+ CONFIG_ARTS=n \
+ CONFIG_ROAR=n \
+ DEBUG=0
+
+override_dh_auto_build:
+ # Pass V=2 to make to enable verbose build logs, which is useful for
+ # porters, sorting out build hardening issues, etc.
+ dh_auto_build -- V=2
+
+override_dh_install:
+ dh_install -pcmus
+ dh_movefiles -pcmus-plugin-ffmpeg --sourcedir=debian/cmus/ \
+ /usr/lib/cmus/ip/ffmpeg.so
+
+override_dh_installdocs:
+ dh_installdocs
+ # do not install zsh and bash completion twice
+ rm debian/cmus/usr/share/doc/cmus/contrib/_cmus \
+ debian/cmus/usr/share/doc/cmus/contrib/cmus.bash-completion
+
+override_dh_shlibdeps:
+ dh_shlibdeps -pcmus $(foreach plugin,$(EXTRA_CMUS_PLUGINS),-X$(plugin))
+ dpkg-shlibdeps -O -dSuggests \
+ $(foreach plugin,$(EXTRA_CMUS_PLUGINS),$(EXTRA_CMUS_DIR_OP_PLUGINS)$(plugin)) \
+ >> debian/cmus.substvars
+ dh_shlibdeps --remaining-packages
--- /dev/null
+---
+include:
+ - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/recipes/debian.yml
--- /dev/null
+3.0 (quilt)
--- /dev/null
+Bug-Database: https://github.com/cmus/cmus/issues
+Bug-Submit: https://github.com/cmus/cmus/issues/new
+Repository: https://github.com/cmus/cmus.git
+Repository-Browse: https://github.com/cmus/cmus
--- /dev/null
+version=4
+opts="dversionmangle=s/\+git\d+$//,uversionmangle=s/([\d\.]+)-(rc|beta)(.*)/\1~\2\3/,filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/cmus-$1\.tar\.gz/" \
+https://github.com/cmus/cmus/tags .*/v?(\d\S*)\.tar\.gz